|
|
|
// Use from 0 to 4. Higher number, more debugging messages and memory usage.
|
|
|
|
//#define _WIFIMGR_LOGLEVEL_ 4
|
|
|
|
//#define DEBUG 1
|
|
|
|
|
|
|
|
#include <WiFi.h>
|
|
|
|
#include <WiFiManager.h>
|
|
|
|
#include "debug.h"
|
|
|
|
#include <Arduino.h>
|
|
|
|
#include <ESPmDNS.h>
|
|
|
|
#include <LiquidCrystal_I2C.h>
|
|
|
|
#include <looper.h>
|
|
|
|
#include <ESPDateTime.h>
|
|
|
|
#include <Time.h>
|
|
|
|
#include <DHT.h>
|
|
|
|
|
|
|
|
#define MDNS_NAME "wlanthermometer"
|
|
|
|
#define AP_SSID_CONFIG_NAME "WLANTHERMOMETER-Config"
|
|
|
|
#define AP_CONFIG_PASSWORD "wlanthermometer"
|
|
|
|
#define AP_DATA_RESET_PIN 25
|
|
|
|
#define TEMP_SENS_PIN 27
|
|
|
|
#define LCD_I2C_ADDR 0x3f
|
|
|
|
#define LCD_COL 20
|
|
|
|
#define LCD_ROW 4
|
|
|
|
#define KRATE_TEMP 5000
|
|
|
|
#define KRATE_TIME 500
|
|
|
|
#define KRATE_RESET_AP_DATA 5000
|
|
|
|
#define MEDIAN_SAMPLES 60
|
|
|
|
#define ONBOARD_LED 2
|
|
|
|
#define NTP_TIMEOUT 15000
|
|
|
|
#define WIFI_CONNECT_TIMEOUT 30
|
|
|
|
#define CFG_PORTAL_TIMEOUT 90
|
|
|
|
#define DHTTYPE DHT22
|
|
|
|
#define FIRST_MIN_MAX 300000 // = 5 min
|
|
|
|
#define HTML_RELOAD_PAGE_SECS 30
|
|
|
|
|
|
|
|
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COL, LCD_ROW);
|
|
|
|
looper sched;
|
|
|
|
float temp[3];
|
|
|
|
float hum[3];
|
|
|
|
float heat[3];
|
|
|
|
bool led_state;
|
|
|
|
String header;
|
|
|
|
WiFiServer server(80);
|
|
|
|
DHT dht(TEMP_SENS_PIN, DHTTYPE);
|
|
|
|
const uint8_t degree_sign[8] = { B00010, B00101, B00010, B00000, B00000, B00000, B00000, B00000 };
|
|
|
|
uint8_t add_summertime = 0;
|
|
|
|
bool last_reset_ap_check = false;
|
|
|
|
bool minmax_enabled = false;
|
|
|
|
|
|
|
|
enum {
|
|
|
|
ACT,
|
|
|
|
MIN,
|
|
|
|
MAX
|
|
|
|
};
|
|
|
|
|
|
|
|
void setup()
|
|
|
|
{
|
|
|
|
pinMode(AP_DATA_RESET_PIN, INPUT_PULLDOWN);
|
|
|
|
pinMode(ONBOARD_LED, OUTPUT);
|
|
|
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
Serial.println(F("WLANThermometer (c)2020 H. Wirtz <wirtz@parasitstudio.de>"));
|
|
|
|
|
|
|
|
lcd.init();
|
|
|
|
lcd.backlight();
|
|
|
|
lcd.clear();
|
|
|
|
lcd.noCursor();
|
|
|
|
lcd.setCursor(2, 0);
|
|
|
|
lcd.print(F("WLAN THERMOMETER"));
|
|
|
|
lcd.setCursor(2, 1);
|
|
|
|
lcd.print(F("(c)parasiTstudio"));
|
|
|
|
|
|
|
|
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
|
|
|
|
|
|
|
|
WiFiManager wm;
|
|
|
|
|
|
|
|
lcd.setCursor(0, 2);
|
|
|
|
lcd.print(F("Connecting WiFi... "));
|
|
|
|
lcd.setCursor(0, 3);
|
|
|
|
lcd.print(F("CFG-AP: 192.168.4.1"));
|
|
|
|
|
|
|
|
wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
|
|
|
|
wm.setConfigPortalTimeout(CFG_PORTAL_TIMEOUT);
|
|
|
|
wm.setBreakAfterConfig(true);
|
|
|
|
|
|
|
|
if (!wm.autoConnect(AP_SSID_CONFIG_NAME, AP_CONFIG_PASSWORD))
|
|
|
|
{
|
|
|
|
lcd.setCursor(0, 2);
|
|
|
|
lcd.print(F("Failed "));
|
|
|
|
DEBUG_MSG("Failed to connect\n");
|
|
|
|
delay(1000);
|
|
|
|
lcd.setCursor(8, 2);
|
|
|
|
lcd.print(F("- restart"));
|
|
|
|
delay(1000);
|
|
|
|
ESP.restart();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_MSG("Connected\n");
|
|
|
|
|
|
|
|
if (!MDNS.begin(MDNS_NAME))
|
|
|
|
{
|
|
|
|
DEBUG_MSG("Error setting up MDNS responder!\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_MSG("mDNS started.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
lcd.clear();
|
|
|
|
lcd.setCursor(0, 0);
|
|
|
|
lcd.print(F("Mode WiFi client"));
|
|
|
|
lcd.setCursor(0, 1);
|
|
|
|
lcd.print(WiFi.localIP());
|
|
|
|
|
|
|
|
delay(500);
|
|
|
|
}
|
|
|
|
|
|
|
|
lcd.setCursor(0, 3);
|
|
|
|
lcd.print("Getting time...");
|
|
|
|
DateTime.setTimeZone(1);
|
|
|
|
DateTime.begin();
|
|
|
|
/* while (!DateTime.isTimeValid())
|
|
|
|
{
|
|
|
|
DEBUG_MSG("Failed to get time from server.\n");
|
|
|
|
DateTime.forceUpdate();
|
|
|
|
}*/
|
|
|
|
if (DateTime.isTimeValid())
|
|
|
|
{
|
|
|
|
if (is_wintertime(DateTime.getTime()) == false)
|
|
|
|
add_summertime = 1;
|
|
|
|
else
|
|
|
|
add_summertime = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a degree sign
|
|
|
|
lcd.createChar(0, (uint8_t*)degree_sign);
|
|
|
|
|
|
|
|
dht.begin();
|
|
|
|
|
|
|
|
server.begin();
|
|
|
|
|
|
|
|
sched.addJob(show_temperature, KRATE_TEMP);
|
|
|
|
sched.addJob(show_time, KRATE_TIME);
|
|
|
|
sched.addJob(check_reset_ap_data, KRATE_RESET_AP_DATA);
|
|
|
|
|
|
|
|
get_sensor_data();
|
|
|
|
temp[MIN] = temp[ACT];
|
|
|
|
temp[MAX] = temp[ACT];
|
|
|
|
hum[MIN] = hum[ACT];
|
|
|
|
hum[MAX] = hum[ACT];
|
|
|
|
heat[MIN] = heat[ACT];
|
|
|
|
heat[MAX] = heat[ACT];
|
|
|
|
|
|
|
|
lcd.clear();
|
|
|
|
show_time();
|
|
|
|
show_temperature();
|
|
|
|
}
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
{
|
|
|
|
static uint32_t next_time_check;
|
|
|
|
|
|
|
|
sched.scheduler();
|
|
|
|
|
|
|
|
if (!DateTime.isTimeValid() && millis() - 10000 > next_time_check)
|
|
|
|
{
|
|
|
|
next_time_check = millis();
|
|
|
|
DateTime.setTimeZone(1);
|
|
|
|
DateTime.forceUpdate();
|
|
|
|
if (is_wintertime(DateTime.getTime()) == false)
|
|
|
|
add_summertime = 1;
|
|
|
|
else
|
|
|
|
add_summertime = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
WiFiClient client = server.available(); // Listen for incoming clients
|
|
|
|
|
|
|
|
if (client) { // If a new client connects,
|
|
|
|
DEBUG_MSG("New Client.\n"); // print a message out in the serial port
|
|
|
|
String currentLine = ""; // make a String to hold incoming data from the client
|
|
|
|
while (client.connected())
|
|
|
|
{ // loop while the client's connected
|
|
|
|
if (client.available())
|
|
|
|
{ // if there's bytes to read from the client,
|
|
|
|
char c = client.read(); // read a byte, then
|
|
|
|
Serial.write(c); // print it out the serial monitor
|
|
|
|
header += c;
|
|
|
|
if (c == '\n')
|
|
|
|
{ // if the byte is a newline character
|
|
|
|
// if the current line is blank, you got two newline characters in a row.
|
|
|
|
// that's the end of the client HTTP request, so send a response:
|
|
|
|
if (currentLine.length() == 0)
|
|
|
|
{
|
|
|
|
// checking if header is valid
|
|
|
|
// YWRtaW46c3RhYWdhcg== (user:pass) admin:staagar
|
|
|
|
if (header.indexOf("YWRtaW46c3RhYWdhcg==") >= 0)
|
|
|
|
{
|
|
|
|
client.println("HTTP/1.1 200 OK");
|
|
|
|
client.println("Content-type:text/html");
|
|
|
|
client.println("Connection: close");
|
|
|
|
client.println();
|
|
|
|
client.println("<!DOCTYPE html>");
|
|
|
|
client.println("<html>");
|
|
|
|
client.println("<head>");
|
|
|
|
client.println("<title>Temperatur Kirche Gartenstadt Staaken</title>");
|
|
|
|
client.print("<meta http-equiv=\"refresh\" content=\"");
|
|
|
|
client.print(HTML_RELOAD_PAGE_SECS, DEC);
|
|
|
|
client.println("\"/>");
|
|
|
|
client.println("<meta http-equiv=\"cache-control\" content=\"no-cache\"/>");
|
|
|
|
client.println("<meta name=\"robots\" content=\"noindex\"/>");
|
|
|
|
client.println("</head>");
|
|
|
|
client.println("<body>");
|
|
|
|
client.println("<center>");
|
|
|
|
client.println("<h1 style=\"color:green;\">Temperatur</h1>");
|
|
|
|
client.print("<h2 style=\"color:green;\">Aktuell: ");
|
|
|
|
client.print(temp[ACT]);
|
|
|
|
client.println(" °C</h2>");
|
|
|
|
if (millis() > FIRST_MIN_MAX)
|
|
|
|
{
|
|
|
|
client.print("<h2 style=\"color:green;\">Minimum: ");
|
|
|
|
client.print(temp[MIN]);
|
|
|
|
client.print(" °C");
|
|
|
|
client.print("</h2>\n");
|
|
|
|
client.print("<h2 style=\"color:green;\">Maximum: ");
|
|
|
|
client.print(temp[MAX]);
|
|
|
|
client.print(" °C");
|
|
|
|
client.println("</h2>");
|
|
|
|
}
|
|
|
|
client.println("<hr>");
|
|
|
|
client.println("<h1 style=\"color:red;\">Gefühlte Temperatur</h1>");
|
|
|
|
client.print("<h2 style=\"color:red;\">Aktuell: ");
|
|
|
|
client.print(heat[ACT]);
|
|
|
|
client.println(" °C</h2>");
|
|
|
|
if (millis() > FIRST_MIN_MAX)
|
|
|
|
{
|
|
|
|
client.print("<h2 style=\"color:red;\">Minimum: ");
|
|
|
|
client.print(heat[MIN]);
|
|
|
|
client.print(" °C");
|
|
|
|
client.println("</h2>");
|
|
|
|
client.print("<h2 style=\"color:red;\">Maximum: ");
|
|
|
|
client.print(heat[MAX]);
|
|
|
|
client.print(" °C");
|
|
|
|
client.println("</h2>");
|
|
|
|
}
|
|
|
|
client.println("<hr>");
|
|
|
|
client.println("<h1 style=\"color:blue;\">Luftfeuchtigkeit</h1>");
|
|
|
|
client.print("<h2 style=\"color:blue;\">Aktuell: ");
|
|
|
|
client.print(hum[ACT]);
|
|
|
|
client.println(" %</h2>");
|
|
|
|
if (millis() > FIRST_MIN_MAX)
|
|
|
|
{
|
|
|
|
client.print("<h2 style=\"color:blue;\">Minimum: ");
|
|
|
|
client.print(hum[MIN]);
|
|
|
|
client.print(" %");
|
|
|
|
client.println("</h2>");
|
|
|
|
client.print("<h2 style=\"color:blue;\">Maximum: ");
|
|
|
|
client.print(hum[MAX]);
|
|
|
|
client.print(" %");
|
|
|
|
client.println("</h2>");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DateTime.isTimeValid())
|
|
|
|
{
|
|
|
|
DateTimeParts p = DateTime.getParts();
|
|
|
|
char dt[21];
|
|
|
|
|
|
|
|
if (is_wintertime(DateTime.getTime()) == false)
|
|
|
|
add_summertime = 1;
|
|
|
|
else
|
|
|
|
add_summertime = 0;
|
|
|
|
|
|
|
|
client.println("<hr>");
|
|
|
|
client.print("<h4 style=\"color:black;\">");
|
|
|
|
|
|
|
|
sprintf(dt, "%02d.%02d.%4d %02d:%02d:%02d", p.getMonthDay(), p.getMonth() + 1, p.getYear(), p.getHours() + add_summertime, p.getMinutes(), p.getSeconds());
|
|
|
|
client.print(dt);
|
|
|
|
if (add_summertime > 0)
|
|
|
|
client.print(" Sommerzeit");
|
|
|
|
else
|
|
|
|
client.print(" Winterzeit");
|
|
|
|
client.println("</h2>");
|
|
|
|
}
|
|
|
|
|
|
|
|
client.println("</center>");
|
|
|
|
client.println("</body>");
|
|
|
|
client.println("</html>");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Wrong user or password, so HTTP request fails...
|
|
|
|
else {
|
|
|
|
client.println("HTTP/1.1 401 Unauthorized");
|
|
|
|
client.println("WWW-Authenticate: Basic realm=\"Secure\"");
|
|
|
|
client.println("Content-Type: text/html");
|
|
|
|
client.println();
|
|
|
|
client.println("<html>Authentication failed</html>");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else { // if you got a newline, then clear currentLine
|
|
|
|
currentLine = "";
|
|
|
|
}
|
|
|
|
} else if (c != '\r') { // if you got anything else but a carriage return character,
|
|
|
|
currentLine += c; // add it to the end of the currentLine
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Clear the header variable
|
|
|
|
header = "";
|
|
|
|
// Close the connection
|
|
|
|
client.stop();
|
|
|
|
DEBUG_MSG("Client disconnected.\n\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void check_reset_ap_data(void)
|
|
|
|
{
|
|
|
|
if (digitalRead(AP_DATA_RESET_PIN) == HIGH && last_reset_ap_check == true)
|
|
|
|
{
|
|
|
|
DEBUG_MSG("Reset AP data\n");
|
|
|
|
|
|
|
|
WiFiManager wm;
|
|
|
|
|
|
|
|
lcd.clear();
|
|
|
|
lcd.setCursor(0, 0);
|
|
|
|
lcd.print("Resetting AP Data");
|
|
|
|
delay(2000);
|
|
|
|
wm.resetSettings();
|
|
|
|
lcd.setCursor(0, 1);
|
|
|
|
lcd.print("Done.");
|
|
|
|
delay(1000);
|
|
|
|
ESP.restart();
|
|
|
|
}
|
|
|
|
else if (digitalRead(AP_DATA_RESET_PIN) == HIGH)
|
|
|
|
{
|
|
|
|
if (digitalRead(AP_DATA_RESET_PIN) == HIGH)
|
|
|
|
DEBUG_MSG("Reset AP data pressed\n");
|
|
|
|
last_reset_ap_check = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
last_reset_ap_check = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_time(void)
|
|
|
|
{
|
|
|
|
char dt[21];
|
|
|
|
|
|
|
|
if (DateTime.isTimeValid())
|
|
|
|
{
|
|
|
|
DateTimeParts p = DateTime.getParts();
|
|
|
|
|
|
|
|
if (p.getHours() == 0 && p.getMinutes() == 0 && p.getSeconds() == 0)
|
|
|
|
DateTime.forceUpdate();
|
|
|
|
|
|
|
|
if ((p.getHours() == 2 || p.getHours() == 3) && p.getMinutes() == 0 && p.getSeconds() == 0)
|
|
|
|
{
|
|
|
|
if (is_wintertime(DateTime.getTime()) == false)
|
|
|
|
add_summertime = 1;
|
|
|
|
else
|
|
|
|
add_summertime = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sprintf(dt, "%02d.%02d.%4d %02d:%02d:%02d", p.getMonthDay(), p.getMonth() + 1, p.getYear(), p.getHours() + add_summertime, p.getMinutes(), p.getSeconds());
|
|
|
|
DEBUG_MSG("%s", dt);
|
|
|
|
if (add_summertime > 0)
|
|
|
|
DEBUG_MSG(" Summertime\n");
|
|
|
|
else
|
|
|
|
DEBUG_MSG(" Wintertime\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
sprintf(dt, "--:--:---- --:--:--");
|
|
|
|
|
|
|
|
lcd.setCursor(0, 0);
|
|
|
|
lcd.print(dt);
|
|
|
|
|
|
|
|
digitalWrite(ONBOARD_LED, led_state);
|
|
|
|
led_state = !led_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_temperature(void)
|
|
|
|
{
|
|
|
|
get_sensor_data();
|
|
|
|
|
|
|
|
DEBUG_MSG("Temperature: %02.2f\n", temp[ACT]);
|
|
|
|
|
|
|
|
lcd.setCursor(0, 1);
|
|
|
|
lcd.print("Temperatur: ");
|
|
|
|
lcd.print(temp[ACT], 1);
|
|
|
|
lcd.write(0);
|
|
|
|
lcd.print("C");
|
|
|
|
if (millis() > FIRST_MIN_MAX)
|
|
|
|
{
|
|
|
|
lcd.setCursor(0, 2);
|
|
|
|
lcd.print("Min: ");
|
|
|
|
lcd.print(temp[MIN], 1);
|
|
|
|
lcd.print(" Max: ");
|
|
|
|
lcd.print(temp[MAX], 1);
|
|
|
|
}
|
|
|
|
lcd.setCursor(0, 3);
|
|
|
|
lcd.print("LF: ");
|
|
|
|
lcd.print(hum[ACT], 1);
|
|
|
|
lcd.print("%");
|
|
|
|
lcd.print(" GT: ");
|
|
|
|
lcd.print(heat[ACT], 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_wintertime(time_t t)
|
|
|
|
{
|
|
|
|
// Sommerzeit/Winterzeit http://manfred.wilzeck.de/Datum_berechnen.html#Jahreszahl
|
|
|
|
// Tag = 31 – ( 4 + Jahr*5 / 4) MOD 7 ' Datum Tag für Beginn Sommerzeit (Jahr 4-stellig)
|
|
|
|
// Tag = 31 – ( 1 + Jahr*5 / 4) MOD 7 ' Datum Tag für Ende Sommerzeit (Jahr 4-stellig)
|
|
|
|
tmElements_t tm;
|
|
|
|
byte day_of_switch[2];
|
|
|
|
time_t switch_secs[2];
|
|
|
|
byte i;
|
|
|
|
|
|
|
|
day_of_switch[0] = 31 - (4 + year(t) * 5 / 4) % 7; // winter->summer
|
|
|
|
day_of_switch[1] = 31 - (1 + year(t) * 5 / 4) % 7; // summer->winter
|
|
|
|
|
|
|
|
for (i = 0; i < 2; i++)
|
|
|
|
{
|
|
|
|
tm.Second = 0;
|
|
|
|
if (i == 0)
|
|
|
|
tm.Hour = 2;
|
|
|
|
else
|
|
|
|
tm.Hour = 3;
|
|
|
|
tm.Minute = 0;
|
|
|
|
tm.Day = day_of_switch[i];
|
|
|
|
if (i == 0)
|
|
|
|
tm.Month = 3;
|
|
|
|
else
|
|
|
|
tm.Month = 10;
|
|
|
|
tm.Year = year(t) - 1970;
|
|
|
|
|
|
|
|
switch_secs[i] = makeTime(tm);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t >= switch_secs[0] && t < switch_secs[1])
|
|
|
|
return (false);
|
|
|
|
else
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void get_sensor_data(void)
|
|
|
|
{
|
|
|
|
temp[ACT] = constrain(dht.readTemperature(), -40.0, 80.0);
|
|
|
|
hum[ACT] = constrain(dht.readHumidity(), 0.0, 100.0);
|
|
|
|
heat[ACT] = constrain(dht.computeHeatIndex(temp[ACT], hum[ACT], false), -40.0, 80.0);
|
|
|
|
|
|
|
|
if (minmax_enabled == false && millis() > FIRST_MIN_MAX)
|
|
|
|
{
|
|
|
|
minmax_enabled = true;
|
|
|
|
temp[MIN] = temp[ACT];
|
|
|
|
temp[MAX] = temp[ACT];
|
|
|
|
hum[MIN] = hum[ACT];
|
|
|
|
hum[MAX] = hum[ACT];
|
|
|
|
heat[MIN] = heat[ACT];
|
|
|
|
heat[MIN] = heat[ACT];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (minmax_enabled == true)
|
|
|
|
{
|
|
|
|
if (temp[ACT] < temp[MIN])
|
|
|
|
temp[MIN] = temp[ACT];
|
|
|
|
if (temp[ACT] > temp[MAX])
|
|
|
|
temp[MAX] = temp[ACT];
|
|
|
|
|
|
|
|
if (hum[ACT] < hum[MIN])
|
|
|
|
hum[MIN] = hum[ACT];
|
|
|
|
if (hum[ACT] > hum[MAX])
|
|
|
|
hum[MAX] = hum[ACT];
|
|
|
|
|
|
|
|
if (heat[ACT] < heat[MIN])
|
|
|
|
heat[MIN] = heat[ACT];
|
|
|
|
if (heat[ACT] > heat[MAX])
|
|
|
|
heat[MAX] = heat[ACT];
|
|
|
|
}
|
|
|
|
}
|