/* * Copyright (c) 2021 Thomas Buck * * This file is part of Giess-o-mat. * * Giess-o-mat is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Giess-o-mat is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Giess-o-mat. If not, see . */ #include #ifdef PLATFORM_ESP #if defined(ARDUINO_ARCH_ESP8266) #include #include #include #elif defined(ARDUINO_ARCH_ESP32) #include #include #include #endif #include #include "wifi.h" #include "config.h" #include "config_pins.h" #include "Functionality.h" #include "SimpleUpdater.h" #include "DebugLog.h" #include "BoolField.h" #include "WifiStuff.h" #ifdef TELEGRAM_TOKEN #include #include //#define TELEGRAM_LOG_TIMINGS #if defined(ARDUINO_ARCH_ESP8266) X509List cert(telegram_cert); #endif WiFiClientSecure secured_client; AsyncTelegram2 bot(secured_client); int64_t trusted_chat_ids[] = TRUSTED_IDS; enum telegram_state { BOT_IDLE, BOT_ASKED_FERT, BOT_ASKED_PLANTS, BOT_ASKED_CONFIRM }; enum telegram_state bot_state = BOT_IDLE; int64_t bot_lock = 0; #endif // TELEGRAM_TOKEN #ifdef MQTT_HOST #include WiFiClient mqttClient; PubSubClient mqtt(mqttClient); unsigned long last_mqtt_reconnect_time = 0; #endif // MQTT_HOST #if defined(TELEGRAM_TOKEN) || defined(MQTT_HOST) static BoolField bot_plants(VALVE_COUNT - 1); static BoolField bot_ferts(PUMP_COUNT); #endif // TELEGRAM_TOKEN || MQTT_HOST UPDATE_WEB_SERVER server(80); WebSocketsServer socket = WebSocketsServer(81); SimpleUpdater updater; unsigned long last_server_handle_time = 0; unsigned long last_websocket_update_time = 0; int wifi_ok = 0; String message_buffer_a; String message_buffer_b; String message_buffer_c; String message_buffer_d; #ifdef ENABLE_GPIO_TEST static bool runningGpioTest = false; static bool gpioTestState = false; unsigned long lastGpioTime = 0; #endif // ENABLE_GPIO_TEST #ifdef ENABLE_INFLUXDB_LOGGING #include Influxdb influx(INFLUXDB_HOST, INFLUXDB_PORT); #endif // ENABLE_INFLUXDB_LOGGING #ifdef ENABLE_GPIO_TEST void runGpioTest(bool state) { lastGpioTime = millis(); for (int i = 0; i < VALVE_COUNT; i++) { get_plants()->getValves()->setPin(i, state); delay(GPIO_TEST_DELAY); } for (int i = 0; i < PUMP_COUNT; i++) { get_plants()->getPumps()->setPin(i, state); if (i < (PUMP_COUNT - 1)) { delay(GPIO_TEST_DELAY); } } } void handleGpioTest() { runningGpioTest = !runningGpioTest; gpioTestState = runningGpioTest; String message = F("GPIOs turned "); message += runningGpioTest ? "on" : "off"; server.send(200, "text/html", message); runGpioTest(gpioTestState); } #endif // ENABLE_GPIO_TEST void wifi_broadcast_state_change(const char *s) { #ifdef TELEGRAM_TOKEN for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) { bot.sendTo(trusted_chat_ids[n], "New state: " + String(s)); } #endif // TELEGRAM_TOKEN #ifdef MQTT_HOST String payload = String("state: ") + s; mqtt.publish("giessomat", payload.c_str()); #endif // MQTT_HOST } #ifdef TELEGRAM_TOKEN String telegram_help() { String s = "Usage:\n"; s += "Send /auto and follow prompts.\n"; s += "Send /abort to cancel menus.\n"; s += "Send /none to skip menus.\n"; s += "Send /begin to confirm menus."; return s; } void telegram_handle_message(TBMessage &msg) { if (!sm_is_idle()) { debug.println("Telegram: message while machine in use"); if (msg.text == "/abort") { sm_bot_abort(); bot.sendTo(msg.chatId, "Aborted current cycle!"); } else { bot.sendTo(msg.chatId, "Machine is already in use.\nPlease try again later."); } return; } if ((bot_state == BOT_IDLE) && (bot_lock == 0)) { bot_lock = msg.chatId; debug.println("Telegram: locked to " + bot_lock); } if (bot_lock != msg.chatId) { debug.println("Telegram: bot locked. abort for chat " + msg.chatId); bot.sendTo(msg.chatId, "Bot is already in use.\nPlease try again later."); return; } if (bot_state == BOT_IDLE) { if (msg.text == "/auto") { String s = "Please enter fertilizer numbers.\n"; s += "Valid numbers: 1 to " + String(PUMP_COUNT) + "\n"; s += "Send /none to skip.\n"; s += "Send /abort to cancel."; bot_ferts.clear(); bot_state = BOT_ASKED_FERT; bot.sendTo(msg.chatId, s); } else if (msg.text == "/abort") { bot_lock = 0; bot.sendTo(msg.chatId, "Nothing to abort."); } else { bot_lock = 0; bot.sendTo(msg.chatId, telegram_help()); } } else if (bot_state == BOT_ASKED_FERT) { if (msg.text == "/abort") { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Aborted."); return; } else if (msg.text != "/none") { String buff; for (int i = 0; i < msg.text.length() + 1; i++) { if ((i == msg.text.length()) || (msg.text[i] == ' ')) { if (buff.length() > 0) { int n = buff.toInt() - 1; buff = ""; bot_ferts.set(n); } } else if ((msg.text[i] >= '0') && (msg.text[i] <= '9')) { buff += msg.text[i]; } else { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Invalid input.\nAborted."); return; } } } String s = "Please enter plant numbers.\n"; s += "Valid numbers: 1 to " + String(VALVE_COUNT - 1) + "\n"; s += "Send /abort to cancel."; bot_plants.clear(); bot_state = BOT_ASKED_PLANTS; bot.sendTo(msg.chatId, s); } else if (bot_state == BOT_ASKED_PLANTS) { if (msg.text == "/abort") { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Aborted."); return; } String buff; for (int i = 0; i < msg.text.length() + 1; i++) { if ((i == msg.text.length()) || (msg.text[i] == ' ')) { if (buff.length() > 0) { int n = buff.toInt() - 1; buff = ""; bot_plants.set(n); } } else if ((msg.text[i] >= '0') && (msg.text[i] <= '9')) { buff += msg.text[i]; } else { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Invalid input.\nAborted."); return; } } if (bot_plants.countSet() <= 0) { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "No plants selected.\nAborted."); return; } #ifdef FULLAUTO_MIN_PLANT_COUNT if (bot_plants.countSet() < FULLAUTO_MIN_PLANT_COUNT) { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Select at least " + String(FULLAUTO_MIN_PLANT_COUNT) + " plants.\nAborted."); return; } #endif String s = "Input accepted.\nFertilizers:"; for (int i = 0; i < PUMP_COUNT; i++) { if (bot_ferts.isSet(i)) { s += " " + String(i + 1); } } s += "\nPlants:"; for (int i = 0; i < (VALVE_COUNT - 1); i++) { if (bot_plants.isSet(i)) { s += " " + String(i + 1); } } s += "\nOk? Send /begin to start or /abort to cancel."; bot_state = BOT_ASKED_CONFIRM; bot.sendTo(msg.chatId, s); } else if (bot_state == BOT_ASKED_CONFIRM) { if (msg.text == "/abort") { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Aborted."); } else if (msg.text == "/begin") { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Auto watering cycle started."); sm_bot_start_auto(bot_ferts, bot_plants); } else { bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Unknown message.\nAborted."); } } else { debug.println("Telegram: invalid state"); bot_state = BOT_IDLE; bot_lock = 0; bot.sendTo(msg.chatId, "Internal error.\nPlease try again."); } } void telegram_handler(TBMessage &msg) { debug.println("Telegram: rx " + String((long int)msg.chatId) + " \"" + msg.text + "\""); bool found = false; for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) { if (trusted_chat_ids[n] == msg.chatId) { found = true; break; } } if (!found) { bot.sendTo(msg.chatId, "Sorry, not authorized!"); return; } telegram_handle_message(msg); } void telegram_poll() { #ifdef TELEGRAM_LOG_TIMINGS unsigned long start = millis(); #endif // TELEGRAM_LOG_TIMINGS TBMessage msg; while (bot.getNewMessage(msg)) { telegram_handler(msg); } #ifdef TELEGRAM_LOG_TIMINGS unsigned long end = millis(); debug.println("Telegram: took " + String(end - start) + "ms"); #endif // TELEGRAM_LOG_TIMINGS } void telegram_hello() { for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) { bot.sendTo(trusted_chat_ids[n], "Giess-o-mat v" FIRMWARE_VERSION " initialized.\nSend /auto to begin."); } } #endif // TELEGRAM_TOKEN #ifdef MQTT_HOST static void mqttCallback(char* topic, byte* payload, unsigned int length) { String ts(topic), ps; for (unsigned int i = 0; i < length; i++) { char c = payload[i]; ps += c; } debug.println("MQTT Rx @ " + ts + ": " + ps); if (ts != "giessomat") { debug.println("MQTT: invalid topic"); return; } if (ps == "abort") { if (sm_is_idle()) { debug.println("MQTT: nothing to abort"); } else { sm_bot_abort(); debug.println("MQTT: user abort"); } } else { if (ps.substring(0, 6) == "state:") { return; } if (ps.substring(0, 4) != "auto") { debug.println("MQTT: invalid payload"); return; } if (!sm_is_idle()) { debug.println("MQTT: machine is in use"); return; } bot_ferts.clear(); bot_plants.clear(); String buff; bool at_plants = false; for (int i = 5; i < ps.length() + 1; i++) { if ((i == ps.length()) || (ps[i] == ' ') || (ps[i] == ',')) { if (buff != "none") { int n = buff.toInt() - 1; if (!at_plants) { bot_ferts.set(n); } else { bot_plants.set(n); } } buff = ""; if ((i < ps.length()) && (ps[i] == ' ')) { at_plants = true; } } else { buff += ps[i]; } } String s = "MQTT: fertilizers:"; for (int i = 0; i < PUMP_COUNT; i++) { if (bot_ferts.isSet(i)) { s += " " + String(i + 1); } } debug.println(s); s = "MQTT: plants:"; for (int i = 0; i < (VALVE_COUNT - 1); i++) { if (bot_plants.isSet(i)) { s += " " + String(i + 1); } } debug.println(s); if (bot_plants.countSet() <= 0) { debug.println("MQTT: no plants selected"); return; } #ifdef FULLAUTO_MIN_PLANT_COUNT if (bot_plants.countSet() < FULLAUTO_MIN_PLANT_COUNT) { debug.println("MQTT: not enough plants selected"); return; } #endif sm_bot_start_auto(bot_ferts, bot_plants); } } static void mqttReconnect() { // Create a random client ID String clientId = F("giessomat-"); clientId += String(random(0xffff), HEX); // Attempt to connect #if defined(MQTT_USER) && defined(MQTT_PASS) if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) { #else if (mqtt.connect(clientId.c_str())) { #endif mqtt.subscribe("giessomat"); } } #endif // MQTT_HOST bool wifi_write_database(int duration, const char *type, int id) { bool success = false; #ifdef ENABLE_INFLUXDB_LOGGING // we still want to be locally usable / have a snappy ui // even when the wifi connection is broken. if (WiFi.status() != WL_CONNECTED) { debug.println("Won't attempt db write, no WiFi connection."); return success; } InfluxData measurement(type); measurement.addTag("version", FIRMWARE_VERSION); measurement.addTag("device", WiFi.macAddress()); measurement.addTag("id", String(id)); measurement.addValue("duration", duration); success = influx.write(measurement); #endif // ENABLE_INFLUXDB_LOGGING if (!success) { debug.print("Error writing to InfluxDB "); debug.print(INFLUXDB_HOST); debug.print(":"); debug.print(INFLUXDB_PORT); debug.print("/"); debug.print(INFLUXDB_DATABASE); debug.print("/"); debug.println(type); } return success; } void wifi_set_message_buffer(String a, String b, String c, String d) { message_buffer_a = a; message_buffer_b = b; message_buffer_c = c; message_buffer_d = d; } void wifi_schedule_websocket(void) { last_websocket_update_time = 0; } void wifi_send_status_broadcast(void) { if (WiFi.status() != WL_CONNECTED) { return; } if (socket.connectedClients() <= 0) { return; } String a = message_buffer_a ; String b = message_buffer_b; String c = message_buffer_c; String d = message_buffer_d; a.replace("\"", "'"); b.replace("\"", "'"); c.replace("\"", "'"); d.replace("\"", "'"); String ws = "{\n"; ws += "\"a\": \"" + a + "\",\n"; ws += "\"b\": \"" + b + "\",\n"; ws += "\"c\": \"" + c + "\",\n"; ws += "\"d\": \"" + d + "\",\n"; ws += "\"state\": \"" + String(control_state_name()) + "\",\n"; ws += F("\"valves\": [ "); for (int i = 0; i < VALVE_COUNT; i++) { ws += "\""; ws += get_plants()->getValves()->getPin(i) ? "1" : "0"; ws += "\""; if (i < (VALVE_COUNT - 1)) { ws += ", "; } } ws += " ],\n"; ws += F("\"pumps\": [ "); for (int i = 0; i < PUMP_COUNT; i++) { ws += "\""; ws += get_plants()->getPumps()->getPin(i) ? "1" : "0"; ws += "\""; if (i < (PUMP_COUNT - 1)) { ws += ", "; } } ws += " ],\n"; ws += F("\"switches\": [ "); for (int i = 0; i < SWITCH_COUNT; i++) { bool v = get_plants()->getSwitches()->getPin(i); #ifdef INVERT_SENSOR_BOTTOM if (i == 0) { v = !v; } #endif // INVERT_SENSOR_BOTTOM #ifdef INVERT_SENSOR_TOP if (i == 1) { v = !v; } #endif // INVERT_SENSOR_TOP ws += "\""; ws += v ? "1" : "0"; ws += "\""; if (i < (SWITCH_COUNT - 1)) { ws += ", "; } } ws += " ],\n"; ws += F("\"aux\": [ "); for (int i = 0; i < AUX_COUNT; i++) { ws += "\""; ws += get_plants()->getAux()->getPin(i) ? "1" : "0"; ws += "\""; if (i < (AUX_COUNT - 1)) { ws += ", "; } } ws += " ],\n"; ws += F("\"kickstart\": [ "); for (int i = 0; i < VALVE_COUNT - 1; i++) { ws += "\""; ws += get_plants()->getKickstart()->getPin(i) ? "1" : "0"; ws += "\""; if (i < (VALVE_COUNT - 2)) { ws += ", "; } } ws += " ],\n"; ws += "\"switchstate\": \""; Plants::Waterlevel wl = get_plants()->getWaterlevel(); if (wl == Plants::empty) { ws += F("tank empty"); } else if (wl == Plants::inbetween) { ws += F("tank half-filled"); } else if (wl == Plants::full) { ws += F("tank full"); } else { ws += F("invalid sensor state"); } ws += "\"\n"; ws += "}"; wifi_send_websocket(ws); } void wifi_send_websocket(String s) { socket.broadcastTXT(s); } #ifdef ARDUINO_ARCH_ESP32 #include // https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/ResetReason/ResetReason.ino static const char *str_reset_reason(int reason) { switch (reason) { case 1: return "POWERON_RESET"; case 3: return "SW_RESET"; case 4: return "OWDT_RESET"; case 5: return "DEEPSLEEP_RESET"; case 6: return "SDIO_RESET"; case 7: return "TG0WDT_SYS_RESET"; case 8: return "TG1WDT_SYS_RESET"; case 9: return "RTCWDT_SYS_RESET"; case 10: return "INTRUSION_RESET"; case 11: return "TGWDT_CPU_RESET"; case 12: return "SW_CPU_RESET"; case 13: return "RTCWDT_CPU_RESET"; case 14: return "EXT_CPU_RESET"; case 15: return "RTCWDT_BROWN_OUT_RESET"; case 16: return "RTCWDT_RTC_RESET"; default: return "NO_MEAN"; } } #endif // ARDUINO_ARCH_ESP32 void handleRoot() { String message = F("\n"); message += F("\n"); message += F("\n"); message += F("\n"); message += F("Gieß-o-mat\n"); message += F("\n"); message += F("\n"); message += F("

Gieß-o-mat

\n"); message += F("
\n"); message += F("
\n"); message += F("
\n");
    message += message_buffer_a + '\n';
    message += message_buffer_b + '\n';
    message += message_buffer_c + '\n';
    message += message_buffer_d + '\n';
    message += F("
\n"); message += F("
\n"); message += F(""); message += F(""); message += F(""); message += F("
\n"); message += F(""); message += F(""); message += F(""); message += F("
\n"); message += F(""); message += F(""); message += F(""); message += F("
\n"); message += F(""); message += F(""); message += F(""); message += F("
\n"); message += F("

\n"); message += F("State: "); message += control_state_name(); message += F("

\n"); message += F("
\n"); message += F("Switches: "); Plants::Waterlevel wl = get_plants()->getWaterlevel(); if (wl == Plants::empty) { message += F("tank empty"); } else if (wl == Plants::inbetween) { message += F("tank half-filled"); } else if (wl == Plants::full) { message += F("tank full"); } else { message += F("invalid sensor state"); } message += F(""); message += F("
\n"); for (int i = 0; i < SWITCH_COUNT; i++) { message += F("
S"); message += String(i + 1); message += F("
"); } message += F("

\n"); message += F("Kickstart:\n"); message += F("
\n"); for (int i = 0; i < VALVE_COUNT - 1; i++) { message += F("
A"); message += String(i + 1); message += F("
"); } message += F("

\n"); message += F("Valves:\n"); message += F("
\n"); for (int i = 0; i < VALVE_COUNT; i++) { message += F("
V"); message += String(i + 1); message += F("
"); } message += F("

\n"); message += F("Pumps:\n"); message += F("
\n"); for (int i = 0; i < PUMP_COUNT; i++) { message += F("
P"); message += String(i + 1); message += F("
"); } message += F("

\n"); message += F("Aux:\n"); message += F("
\n"); for (int i = 0; i < AUX_COUNT; i++) { message += F("
A"); message += String(i + 1); message += F("
"); } message += F("

\n"); message += F("Green means valve is closed / pump is off / switch is not submersed.\n"); message += F("
\n"); message += F("Red means valve is open / pump is running / switch is submersed.
\n"); message += F("

\n"); message += F("Version: "); message += FIRMWARE_VERSION; message += F("\n
\n"); message += F("Build Date: "); message += __DATE__; message += F("\n
\n"); message += F("Build Time: "); message += __TIME__; message += F("\n

\n"); message += F("MAC: "); message += WiFi.macAddress(); message += F("\n
\n"); message += F("IPv4: "); message += WiFi.localIP().toString(); message += F("\n

\n"); #if defined(ARDUINO_ARCH_ESP8266) message += F("\n

\n"); message += F("Reset reason: "); message += ESP.getResetReason(); message += F("\n
\n"); message += F("Free heap: "); message += String(ESP.getFreeHeap()); message += F(" ("); message += String(ESP.getHeapFragmentation()); message += F("% fragmentation)"); message += F("\n
\n"); message += F("Free sketch space: "); message += String(ESP.getFreeSketchSpace()); message += F("\n
\n"); message += F("Flash chip real size: "); message += String(ESP.getFlashChipRealSize()); if (ESP.getFlashChipSize() != ESP.getFlashChipRealSize()) { message += F("\n
\n"); message += F("WARNING: sdk chip size ("); message += (ESP.getFlashChipSize()); message += F(") does not match!"); } message += F("\n

\n"); #elif defined(ARDUINO_ARCH_ESP32) message += F("\n

\n"); message += F("Reset reason: "); message += str_reset_reason(rtc_get_reset_reason(0)); message += F(" "); message += str_reset_reason(rtc_get_reset_reason(1)); message += F("\n
\n"); message += F("Free heap: "); message += String(ESP.getFreeHeap() / 1024.0); message += F("k\n
\n"); message += F("Free sketch space: "); message += String(ESP.getFreeSketchSpace() / 1024.0); message += F("k\n
\n"); message += F("Flash chip size: "); message += String(ESP.getFlashChipSize() / 1024.0); message += F("k\n

\n"); #endif message += F("

\n"); #ifdef TELEGRAM_TOKEN message += F("Telegram: "); message += TELEGRAM_UPDATE_INTERVAL; message += F("ms\n"); #else message += F("Telegram bot not enabled!\n"); #endif message += F("

\n"); message += F("

\n"); #ifdef MQTT_HOST message += F("MQTT: "); message += MQTT_HOST; message += F(":"); message += String(MQTT_PORT); message += F("\n"); #else message += F("MQTT not enabled!\n"); #endif message += F("

\n"); message += F("

\n"); #ifdef ENABLE_INFLUXDB_LOGGING message += F("InfluxDB: "); message += INFLUXDB_DATABASE; message += F(" @ "); message += INFLUXDB_HOST; message += F(":"); message += String(INFLUXDB_PORT); message += F("\n"); #else message += F("InfluxDB logging not enabled!\n"); #endif message += F("

\n"); message += F("

Try /update for OTA firmware updates!

\n"); message += F("

Made by xythobuz

\n"); message += F("

Project Repository

\n"); #ifdef ENABLE_GPIO_TEST message += F("

GPIO Test

\n"); #endif // ENABLE_GPIO_TEST message += F("
\n"); message += F("
\n");
    message += debug.getBuffer();
    message += F("
\n"); message += F("\n"); message += F("\n"); message += F("\n"); server.send(200, "text/html", message); } void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { if ((type != WStype_TEXT) || (length != 1)) { debug.println("Websocket: invalid type=" + String(type) + " len=" + String(length) /* + " data=" + String((char *)payload) */ ); return; } char c = payload[0]; if ((c >= '0') && (c <= '9')) { control_act_input(c - '0'); } else if (c == '*') { control_act_input(-1); } else if (c == '#') { control_act_input(-2); } } void wifi_setup() { // Build hostname string String hostname = "giess-o-mat"; int ws = 0, connect_attempts = 0; #if defined(ARDUINO_ARCH_ESP8266) // Connect to WiFi AP debug.println("WiFi: initializing"); WiFi.hostname(hostname); WiFi.mode(WIFI_STA); debug.print("WiFi: connecting"); WiFi.begin(WIFI_SSID, WIFI_PW); #ifdef TELEGRAM_TOKEN secured_client.setTrustAnchors(&cert); #endif // TELEGRAM_TOKEN while (((ws = WiFi.status()) != WL_CONNECTED) && (connect_attempts < MAX_WIFI_CONNECT_ATTEMPTS)) { connect_attempts++; debug.print(String(" ") + String(ws)); delay(LED_CONNECT_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN)); } debug.println(); debug.println(String("WiFi: status=") + String(WiFi.status())); #elif defined(ARDUINO_ARCH_ESP32) // Set hostname workaround debug.println("WiFi: set hostname"); WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); WiFi.setHostname(hostname.c_str()); // Workaround for WiFi connecting only every 2nd reset // https://github.com/espressif/arduino-esp32/issues/2501#issuecomment-513602522 debug.println("WiFi: connection work-around"); WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { if (info.disconnected.reason == 202) { esp_sleep_enable_timer_wakeup(10); esp_deep_sleep_start(); delay(100); } }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); // Connect to WiFi AP debug.println("WiFi: SSID=" WIFI_SSID); debug.print("WiFi: connecting"); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PW); #ifdef TELEGRAM_TOKEN secured_client.setCACert(telegram_cert); #endif // TELEGRAM_TOKEN while (((ws = WiFi.status()) != WL_CONNECTED) && (connect_attempts < MAX_WIFI_CONNECT_ATTEMPTS)) { connect_attempts++; debug.print(String(" ") + String(ws)); delay(LED_CONNECT_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN)); } debug.println(); debug.println(String("WiFi: status=") + String(WiFi.status())); // Set hostname workaround debug.println("WiFi: set hostname work-around"); WiFi.setHostname(hostname.c_str()); #endif if ((connect_attempts >= MAX_WIFI_CONNECT_ATTEMPTS) || (WiFi.status() != WL_CONNECTED)) { debug.println("WiFi: init failed!"); wifi_ok = 0; return; } wifi_ok = 1; debug.print("WiFi: got IPv4: "); debug.println(WiFi.localIP().toString()); #ifdef ENABLE_INFLUXDB_LOGGING // Setup InfluxDB Client debug.println("WiFi: set InfluxDB database"); influx.setDb(INFLUXDB_DATABASE); #endif // ENABLE_INFLUXDB_LOGGING // Setup HTTP Server debug.println("WiFi: initializing HTTP server"); MDNS.begin(hostname.c_str()); updater.setup(&server); server.on("/", handleRoot); #ifdef ENABLE_GPIO_TEST server.on("/gpiotest", handleGpioTest); #endif // ENABLE_GPIO_TEST #ifdef TELEGRAM_TOKEN debug.print("WiFi: getting NTP time"); configTime(0, 0, "pool.ntp.org"); time_t now = time(nullptr); while (now < 24 * 60 * 60) { debug.print("."); delay(100); now = time(nullptr); } debug.println(" done!"); debug.println("WiFi: time is " + String(now)); debug.print("WiFi: initializing Telegram... "); bot.setUpdateTime(TELEGRAM_UPDATE_INTERVAL); bot.setTelegramToken(TELEGRAM_TOKEN); bot.begin() ? debug.println("Ok") : debug.println("Error"); bot.setMyCommands("auto", "Start automatic watering cycle"); bot.setMyCommands("confirm", "Proceed with any menu inputs"); bot.setMyCommands("none", "Proceed without menu input"); bot.setMyCommands("abort", "Cancel any menu inputs"); telegram_hello(); #endif // TELEGRAM_TOKEN #ifdef MQTT_HOST debug.println("WiFi: initializing MQTT"); mqtt.setServer(MQTT_HOST, MQTT_PORT); mqtt.setCallback(mqttCallback); #endif // MQTT_HOST server.begin(); MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 81); socket.begin(); socket.onEvent(webSocketEvent); debug.println("WiFi: setup done"); } void wifi_run() { if (!wifi_ok) { // nothing to handle return; } if ((millis() - last_server_handle_time) >= SERVER_HANDLE_INTERVAL) { last_server_handle_time = millis(); server.handleClient(); socket.loop(); #ifdef ARDUINO_ARCH_ESP8266 MDNS.update(); #endif // ARDUINO_ARCH_ESP8266 #ifdef TELEGRAM_TOKEN telegram_poll(); #endif // TELEGRAM_TOKEN } if ((millis() - last_websocket_update_time) >= WEBSOCKET_UPDATE_INTERVAL) { last_websocket_update_time = millis(); wifi_send_status_broadcast(); } #ifdef ENABLE_GPIO_TEST if (runningGpioTest && ((millis() - lastGpioTime) >= GPIO_TEST_INTERVAL)) { gpioTestState = !gpioTestState; runGpioTest(gpioTestState); } #endif // ENABLE_GPIO_TEST #ifdef MQTT_HOST if (!mqtt.connected() && ((millis() - last_mqtt_reconnect_time) >= MQTT_RECONNECT_INTERVAL)) { last_mqtt_reconnect_time = millis(); mqttReconnect(); } mqtt.loop(); #endif // MQTT_HOST } #endif // PLATFORM_ESP