/* * main.cpp * * ESP8266 / ESP32 Relais Actor * * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice * you can do whatever you want with this stuff. If we meet some day, and you * think this stuff is worth it, you can buy me a beer in return. Thomas Buck * ---------------------------------------------------------------------------- */ #include #include #include #if defined(ARDUINO_ARCH_ESP8266) #include #include #include #elif defined(ARDUINO_ARCH_ESP32) #include #include #include #endif #include "config.h" #include "relais.h" #include "SimpleUpdater.h" #define BUILTIN_LED_PIN 1 UPDATE_WEB_SERVER server(80); SimpleUpdater updater; #ifdef ENABLE_MQTT WiFiClient mqttClient; PubSubClient mqtt(mqttClient); #endif // ENABLE_MQTT #ifdef ENABLE_INFLUXDB_LOGGING #include Influxdb influx(INFLUXDB_HOST, INFLUXDB_PORT); #define INFLUX_MAX_ERRORS_RESET 10 int error_count = 0; void writeDatabase(); #endif // ENABLE_INFLUXDB_LOGGING #ifdef ENABLE_NTP #include #include WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 2 * 60 * 60, 5 * 60 * 1000); #endif // ENABLE_NTP unsigned long last_server_handle_time = 0; unsigned long last_db_write_time = 0; unsigned long last_led_blink_time = 0; unsigned long last_timing_check_time = 0; unsigned long last_mqtt_reconnect_time = 0; String relais_name[RELAIS_COUNT] = { String(" (Small Light)"), String(" (Big Light)"), String(""), String(" (Fan)"), }; bool relais_state[RELAIS_COUNT] = { false, false, false, false }; String auto_set_state = String(""); #ifdef ENABLE_NTP void handleAutoTimingCheck(void) { relais_state[3] = true; // always keep fan on if ((timeClient.getHours() >= 20) || (timeClient.getHours() < 10)) { // small light from 20:00 to 10:00 relais_state[0] = true; relais_state[1] = false; auto_set_state = String(F("Small Light at Night (")) + String(timeClient.getFormattedTime()) + String(F(")")); } else { // big light from 10:00 to 20:00 relais_state[0] = false; relais_state[1] = true; auto_set_state = String(F("Big Light while Daylight (")) + String(timeClient.getFormattedTime()) + String(F(")")); } for (int i = 0; i < RELAIS_COUNT; i++) { relais_set(i, relais_state[i] ? 1 : 0); } } #endif // ENABLE_NTP void handlePage(int mode = -1, int id = 0) { String message = F("\n"); message += F("" ESP_PLATFORM_NAME " MQTT Relais\n"); message += F("\n"); message += F("

" ESP_PLATFORM_NAME " MQTT Relais

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

\n"); for (int i = 0; i < RELAIS_COUNT; i++) { message += String(F("Relais ")) + String(i) + String(F(" On")) + relais_name[i] + String(F("
\n")); message += String(F("Relais ")) + String(i) + String(F(" Off")) + relais_name[i] + String(F("
\n")); } message += String(F("All Relais On
\n")); message += String(F("All Relais Off
\n")); message += F("

\n"); if (mode >= 0) { message += F("

"); message += F("Turned Relais "); message += (id < RELAIS_COUNT) ? String(id) : String(F("1-4")); message += (mode ? String(F(" On")) : String(F(" Off"))); message += F("

\n"); } message += F("\n

\n"); for (int i = 0; i < RELAIS_COUNT; i++) { message += String(F("Relais ")) + String(i) + relais_name[i] + String(F(" = ")) + (relais_state[i] ? String(F("On")) : String(F("Off"))) + String(F("
\n")); } message += F("

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

Time from NTP: "); message += String(timeClient.getFormattedTime()); message += F("

\n"); message += F("

Auto-State: "); message += auto_set_state; message += F("

\n"); unsigned long next_min = (AUTO_TIMING_INTERVAL - (millis() - last_timing_check_time)) / (1000 * 60); message += F("

Next Auto-State in "); message += String(next_min); message += F("min

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

Refresh Page

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

\n"); message += F("Version: "); message += ESP_RELAIS_VERSION; message += F("\n
\n"); message += F("Location: "); message += SENSOR_LOCATION; message += F("\n
\n"); message += F("MAC: "); message += WiFi.macAddress(); 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("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"); message += F("Try /update for OTA firmware updates!\n"); 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("\n"); server.send(200, "text/html", message); } void handleOn() { String id_string = server.arg("id"); int id = id_string.toInt(); if ((id >= 0) && (id < RELAIS_COUNT)) { relais_set(id, 1); relais_state[id] = true; } else { for (int i = 0; i < RELAIS_COUNT; i++) { relais_set(i, 1); relais_state[i] = true; } } // only reset to default after 15min last_timing_check_time = millis(); #ifdef ENABLE_INFLUXDB_LOGGING writeDatabase(); #endif // ENABLE_INFLUXDB_LOGGING handlePage(1, id); } void handleOff() { String id_string = server.arg("id"); int id = id_string.toInt(); if ((id >= 0) && (id < RELAIS_COUNT)) { relais_set(id, 0); relais_state[id] = false; } else { for (int i = 0; i < RELAIS_COUNT; i++) { relais_set(i, 0); relais_state[i] = false; } } // only reset to default after 15min last_timing_check_time = millis(); #ifdef ENABLE_INFLUXDB_LOGGING writeDatabase(); #endif // ENABLE_INFLUXDB_LOGGING handlePage(0, id); } void handleRoot() { handlePage(); } #ifdef ENABLE_MQTT void mqttCallback(char* topic, byte* payload, unsigned int length) { int state = 0; int id = 0; String ts(topic), ps((char *)payload); String our_topic(SENSOR_LOCATION); our_topic += "/"; if (!ts.startsWith(our_topic)) { return; } String ids = ts.substring(our_topic.length()); id = ids.toInt(); if ((id < 0) || (id >= RELAIS_COUNT)) { return; } if (ps.indexOf("on") != -1) { state = 1; } else if (ps.indexOf("off") != -1) { state = 0; } else { return; } if ((id >= 0) && (id < RELAIS_COUNT)) { relais_set(id, state); relais_state[id] = state ? true : false; #ifdef ENABLE_INFLUXDB_LOGGING writeDatabase(); #endif // ENABLE_INFLUXDB_LOGGING } } void mqttReconnect() { // Create a random client ID String clientId = "ESP8266-Relais-"; 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 // Once connected, publish an announcement... mqtt.publish(SENSOR_LOCATION, "hello world"); // ... and resubscribe mqtt.subscribe(SENSOR_LOCATION); for (int i = 0; i < RELAIS_COUNT; i++) { String topic(SENSOR_LOCATION); topic += String("/") + String(i); mqtt.subscribe(topic.c_str()); } } } #endif // ENABLE_MQTT void setup() { pinMode(BUILTIN_LED_PIN, OUTPUT); relais_init(); for (int i = 0; i < RELAIS_COUNT; i++) { relais_state[i] = false; } Serial.println(); Serial.println("Hello World From Relais Test"); // Blink LED for init for (int i = 0; i < 2; i++) { digitalWrite(BUILTIN_LED_PIN, LOW); // LED on delay(LED_INIT_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off delay(LED_INIT_BLINK_INTERVAL); } // Build hostname string String hostname = SENSOR_HOSTNAME_PREFIX; hostname += SENSOR_LOCATION; #if defined(ARDUINO_ARCH_ESP8266) // Connect to WiFi AP Serial.print("Connecting to WiFi "); WiFi.hostname(hostname); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { Serial.print(WiFi.status()); Serial.print(" "); delay(LED_CONNECT_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN)); } Serial.println(); Serial.print("Got local IP "); Serial.println(WiFi.localIP().toString()); #elif defined(ARDUINO_ARCH_ESP32) // Set hostname workaround 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 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 Serial.print("Connecting to WiFi"); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(LED_CONNECT_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN)); } Serial.println(); // Set hostname workaround WiFi.setHostname(hostname.c_str()); #endif Serial.println("Seeding"); randomSeed(micros()); #ifdef ENABLE_MQTT Serial.println("MQTT"); mqtt.setServer(MQTT_HOST, MQTT_PORT); mqtt.setCallback(mqttCallback); #endif // ENABLE_MQTT #ifdef ENABLE_INFLUXDB_LOGGING // Setup InfluxDB Client influx.setDb(INFLUXDB_DATABASE); #endif // ENABLE_INFLUXDB_LOGGING #ifdef ENABLE_NTP timeClient.begin(); #endif // ENABLE_NTP // Setup HTTP Server Serial.println("Server"); MDNS.begin(hostname.c_str()); updater.setup(&server); server.on("/", handleRoot); server.on("/on", handleOn); server.on("/off", handleOff); server.begin(); MDNS.addService("http", "tcp", 80); Serial.println("Done"); } void handleServers() { server.handleClient(); #if defined(ARDUINO_ARCH_ESP8266) MDNS.update(); #endif } #ifdef ENABLE_INFLUXDB_LOGGING static boolean writeMeasurement(InfluxData &measurement) { boolean success = influx.write(measurement); if (!success) { error_count++; for (int i = 0; i < 10; i++) { digitalWrite(BUILTIN_LED_PIN, LOW); // LED on delay(LED_ERROR_BLINK_INTERVAL); digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off delay(LED_ERROR_BLINK_INTERVAL); } } return success; } void writeDatabase() { for (int i = 0; i < RELAIS_COUNT; i++) { InfluxData measurement("relais"); measurement.addTag("location", SENSOR_LOCATION); measurement.addTag("device", WiFi.macAddress()); measurement.addTag("id", String(i)); measurement.addValue("state", relais_state[i] ? 1 : 0); writeMeasurement(measurement); } } #endif // ENABLE_INFLUXDB_LOGGING void loop() { if ((millis() - last_server_handle_time) >= SERVER_HANDLE_INTERVAL) { last_server_handle_time = millis(); handleServers(); } #ifdef ENABLE_NTP timeClient.update(); if (timeClient.isTimeSet()) { if (((millis() - last_timing_check_time) >= AUTO_TIMING_INTERVAL) || (last_timing_check_time == 0)) { last_timing_check_time = millis(); handleAutoTimingCheck(); } } #endif // ENABLE_NTP #ifdef ENABLE_MQTT if (!mqtt.connected() && ((millis() - last_mqtt_reconnect_time) >= MQTT_RECONNECT_INTERVAL)) { last_mqtt_reconnect_time = millis(); mqttReconnect(); } mqtt.loop(); #endif // ENABLE_MQTT #ifdef ENABLE_INFLUXDB_LOGGING #if DB_WRITE_INTERVAL > 0 if ((millis() - last_db_write_time) >= DB_WRITE_INTERVAL) { last_db_write_time = millis(); writeDatabase(); } #endif // DB_WRITE_INTERVAL > 0 #ifdef INFLUX_MAX_ERRORS_RESET if (error_count >= INFLUX_MAX_ERRORS_RESET) { ESP.restart(); } #endif // INFLUX_MAX_ERRORS_RESET #endif // ENABLE_INFLUXDB_LOGGING // blink heartbeat LED if ((millis() - last_led_blink_time) >= LED_BLINK_INTERVAL) { last_led_blink_time = millis(); digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN)); } }