/*
 * Blink
 * Turns on an LED on for one second,
 * then off for one second, repeatedly.
 */

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>

#include <Wire.h>
#include <SPI.h>

#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/Org_01.h>
#include "bitmaps/Bitmaps128x250.h"
#include <Adafruit_GFX.h>

#include <Adafruit_Sensor.h>
#include "Adafruit_BME280.h"
#include "Adafruit_BME680.h"

#include "Adafruit_VEML6075.h"
#include <BH1750.h>

#define ARDUINO_SAMD_VARIANT_COMPLIANCE
#include "SdsDustSensor.h"

#include "network/XD0OTA.h"
#include "network/XD0MQTT.h"

#include "SensorHistory.h"

#include "icons.h"

extern "C" {
uint8_t temprature_sens_read();
}

static const char* TAG = "MAIN";

WiFiMulti wifiMulti;
GxEPD2_BW<GxEPD2_213_B72, GxEPD2_213_B72::HEIGHT> display(GxEPD2_213_B72(/*CS=SS*/ TFT_CS, /*DC=*/ TFT_DC, /*RST=*/ TFT_RST, /*BUSY=*/ -1)); // GDEH0213B72
static constexpr uint8_t y_offset = 8;

Adafruit_BME280 bme280; // I2C (also available: hardware SPI
Adafruit_BME680 bme680; // I2C (also available: hardware SPI
//HardwareSerial Serial2(2);
SdsDustSensor sds(Serial2);
Adafruit_VEML6075 uv = Adafruit_VEML6075();
BH1750 lightMeter;

XD0OTA ota("esp32-weatherstation");
XD0MQTT mqtt;

struct __attribute__((packed)) sensor_readings_t {
  float temperature = NAN; // °C
  float humidity = NAN; // %H
  float pressure = NAN; // hPa
  float pressure_raw = NAN; // Pa
  uint32_t voc = 0; // Ohm
  float pm10 = NAN; // µg/m³
  float pm25 = NAN; // µg/m³
  float lux = NAN;
  float uvi = NAN;
  float uva = NAN;
  float uvb = NAN;
  float temperature_max = NAN; // °C
  float temperature_min = NAN; // °C
  int battery = 0;
  uint32_t lastUpdate = 0;
} sensor_readings;

sensor_readings_t sensors_a4cf1211c3e4, sensors_246f28d1fa5c, sensors_246f28d1a080, sensors_246f28d1eff4;

SensorHistory history_pressure(30);

uint32_t lastDisplayUpdate = 0;
uint32_t lastDisplayRefresh = 0;
bool bme280_active = false;
bool bme680_active = false;
bool uv_active = false;
bool light_active = false;
bool sds_active = false;

float station_height = 0;

void helloWorld()
{
  const char HelloWorld[] = "IchbinsBens!";
  //Serial.println("helloWorld");
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  // center bounding box by transposition of origin:
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  display.setFullWindow();
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(HelloWorld);

    display.setCursor(5, display.height()-5);
    display.setFont(&Org_01);
    display.print(FW_VERSION);
  }
  while (display.nextPage());
  //Serial.println("helloWorld done");
}


void displayIcoPartial(const uint8_t bitmap[], uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
  display.setPartialWindow(x, y, w, h);
  display.firstPage(); do {
    display.drawInvertedBitmap(x, y, bitmap, w, h, GxEPD_BLACK);
  } while (display.nextPage());
}


void getTime(char* ptr, size_t maxsize, const char* format) {
  time_t now;
  struct tm timeinfo;
  time(&now);        // update 'now' variable with current time
  setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
  tzset();
  localtime_r(&now, &timeinfo);

  strftime(ptr, maxsize, format, &timeinfo);
}


void getSensorMeasurements() {
  if (bme280_active) {
    bme280.takeForcedMeasurement();
    sensor_readings.temperature = bme280.readTemperature();
    sensor_readings.humidity = bme280.readHumidity();
    sensor_readings.pressure_raw = bme280.readPressure();
  }
  if (bme680_active) {
    if (bme680.performReading()) {
      sensor_readings.temperature = bme680.temperature;
      sensor_readings.humidity = bme680.humidity;
      sensor_readings.pressure_raw = bme680.pressure;
      sensor_readings.voc = bme680.gas_resistance;
    } else {
      Serial.println("Failed to perform reading :(");
    }
  }

  if (sensor_readings.temperature > sensor_readings.temperature_max
      || isnan(sensor_readings.temperature_max)) {
    sensor_readings.temperature_max = sensor_readings.temperature;
  }
  if (sensor_readings.temperature < sensor_readings.temperature_min
      || isnan(sensor_readings.temperature_min)) {
    sensor_readings.temperature_min = sensor_readings.temperature;
  }

  // https://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Reduktion_auf_Meeresh%C3%B6he
  // https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
  float absolute_humidity = (6.112*exp((17.67*sensor_readings.temperature)/(sensor_readings.temperature+243.5))*(sensor_readings.humidity/100)*18.02)/((273.15+sensor_readings.temperature)*1000*0.08314);
  float pressure_compensation_factor = exp((9.80665/(287.05*(sensor_readings.temperature+273.15+0.12*((absolute_humidity*461.5*(sensor_readings.temperature+273.15)/100)/100)+0.0065*(station_height/2))))*station_height);

  sensor_readings.pressure = (sensor_readings.pressure_raw / 100.0F) * pressure_compensation_factor;

  history_pressure.addValue(sensor_readings.pressure);

  if (uv_active) {
    sensor_readings.uvi = uv.readUVI();
    sensor_readings.uva = uv.readUVA();
    sensor_readings.uvb = uv.readUVB();
  }

  if (light_active) {
    sensor_readings.lux = lightMeter.readLightLevel();
    // auto-adjust sensitivity
    if (sensor_readings.lux < 0) {
      Serial.println("Error reading light level");
    } else if (sensor_readings.lux > 40000.0) {
      if (lightMeter.setMTreg(32)) {
        Serial.println(F("Setting MTReg to low value for high light environment"));
      }
    } else if (sensor_readings.lux <= 10.0) {
      if (lightMeter.setMTreg(138)) {
        Serial.println(F("Setting MTReg to high value for low light environment"));
      }
    } else { // if (sensor_readings.lux > 10.0)
      if (lightMeter.setMTreg(69)) {
        Serial.println(F("Setting MTReg to default value for normal light environment"));
      }
    }
  }

  if (sds_active) {
    PmResult pm = sds.readPm();
    if (pm.isOk()) {
      sensor_readings.pm10 = pm.pm10;
      sensor_readings.pm25 = pm.pm25;
    }
  }

  sensor_readings.battery = analogRead(_VBAT);

  sensor_readings.lastUpdate = millis();
}


void receiveMqtt(const char* topic, const char* data) {
    sensor_readings_t* sensor = NULL;

    if (strstr(topic, "thomas/sensor/a4cf1211c3e4") == topic) {
      sensor = &sensors_a4cf1211c3e4;
    } else if (strstr(topic, "thomas/sensor/246f28d1fa5c") == topic) {
      sensor = &sensors_246f28d1fa5c;
    } else if (strstr(topic, "thomas/sensor/246f28d1a080") == topic) {
      sensor = &sensors_246f28d1a080;
    } else if (strstr(topic, "thomas/sensor/246f28d1eff4") == topic) {
      sensor = &sensors_246f28d1eff4;
    }

    char* topic_last = strrchr(topic, '/');

    if (topic_last && sensor) {
      if (strcmp("/temperature", topic_last) == 0) {
        sensor->temperature = atof(data);
      } else if (strcmp("/humidity", topic_last) == 0) {
        sensor->humidity = atof(data);
      } else if (strcmp("/pressure", topic_last) == 0) {
        sensor->pressure = atof(data);
      } else if (strcmp("/pm10", topic_last) == 0) {
        sensor->pm10 = atof(data);
      } else if (strcmp("/pm25", topic_last) == 0) {
        sensor->pm25 = atof(data);
      } else if (strcmp("/uvi", topic_last) == 0) {
        sensor->uvi = atof(data);
      } else if (strcmp("/uva", topic_last) == 0) {
        sensor->uva = atof(data);
      } else if (strcmp("/uvb", topic_last) == 0) {
        sensor->uvb = atof(data);
      } else if (strcmp("/voc", topic_last) == 0) {
        sensor->voc = atof(data);
      }
      sensor->lastUpdate = millis();
    }
}


void displayValues() {
  display.setRotation(1);
  display.setFont(NULL);
  display.setTextColor(GxEPD_BLACK);
  display.setTextSize(1);
  display.setTextWrap(false);

  char timeStr[40];
  getTime(timeStr, sizeof(timeStr), "%d. %b %Y  %H:%M:%S");

  display.setFullWindow();
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);

    // Title
    display.setCursor(30,y_offset+0);
    display.println(timeStr);
    display.drawLine(0,y_offset+10,display.width(), y_offset+10, GxEPD_BLACK);

    // Temp
    display.drawRect(0,y_offset+10,66,50,GxEPD_BLACK);
    display.setFont(NULL);
    display.setCursor(5,y_offset+15);
    display.printf("max: %.1f", sensor_readings.temperature_max);
    display.setFont(&FreeSansBold9pt7b);
    display.setCursor(5,y_offset+40);
    display.printf("%.1f", sensor_readings.temperature);
    display.setFont(NULL);
    display.print(" \xf7\x43");
    display.setCursor(5,y_offset+45);
    display.printf("min: %.1f", sensor_readings.temperature_min);

    // Humidity
    display.drawRect(65,y_offset+10,66,50,GxEPD_BLACK);
    display.setFont(NULL);
    display.setCursor(70,y_offset+15);
    display.print("Humidity");
    display.setFont(&FreeSansBold9pt7b);
    display.setCursor(70,y_offset+40);
    display.printf("%.1f", sensor_readings.humidity);
    display.setFont(NULL);
    display.print(" \%");
    display.setCursor(70,y_offset+45);
    if (sensor_readings.humidity < 30) {
      display.print("low");
    } else if (sensor_readings.humidity < 60) {
      display.print("comfort");
    } else {
      display.print("high");
    }


    // Pressure
    display.drawRect(130,y_offset+10,66,50,GxEPD_BLACK);
    display.setFont(NULL);
    display.setCursor(135,y_offset+15);
    display.print("Pressure");
    display.setFont(&FreeSansBold9pt7b);
    display.setCursor(135,y_offset+40);
    display.printf("%.1f", sensor_readings.pressure);
    display.setFont(NULL);
    //display.print(" hPa");
    float pressure_diff = history_pressure.getElement(0) - history_pressure.getFirst();
    display.setCursor(135,y_offset+45);
    if (isnan(pressure_diff) || history_pressure.getCount() < history_pressure.getSize()) {
    } else if (pressure_diff > -20 && pressure_diff < -0.6) {
      display.print("Trend: \x19\x19");
    } else if (pressure_diff < -0.1) {
      display.print("Trend: \x19");
    } else if (pressure_diff < 0.1) {
      display.print("Trend: \x1a");
    } else if (pressure_diff < 0.6) {
      display.print("Trend: \x18");
    } else if (pressure_diff < 20) {
      display.print("Trend: \x18\x18");
    } else {
      display.print("?");
    }


    // Other
    display.drawRect(195,y_offset+10,56,122-10,GxEPD_BLACK);
    display.setFont(NULL);
    // VOC
    display.setCursor(200,y_offset+15);
    display.println("VOC:");
    display.setCursor(200,y_offset+25);
    display.printf("%.1f k\xe9", sensor_readings.voc / 1000.0F);
    // PM
    float pm10, pm25;
    if (sds_active) {
      pm10 = sensor_readings.pm10;
      pm25 = sensor_readings.pm25;
    } else if (!isnan(sensors_a4cf1211c3e4.pm10) || !isnan(sensors_a4cf1211c3e4.pm25)) {
      pm10 = sensors_a4cf1211c3e4.pm10;
      pm25 = sensors_a4cf1211c3e4.pm25;
    } else if (!isnan(sensors_246f28d1fa5c.pm10) || !isnan(sensors_246f28d1fa5c.pm25)) {
      pm10 = sensors_246f28d1fa5c.pm10;
      pm25 = sensors_246f28d1fa5c.pm25;
    } else if (!isnan(sensors_246f28d1a080.pm10) || !isnan(sensors_246f28d1a080.pm25)) {
      pm10 = sensors_246f28d1a080.pm10;
      pm25 = sensors_246f28d1a080.pm25;
    } else {
      pm10 = NAN;
      pm25 = NAN;
    }
    display.setCursor(200,y_offset+45);
    display.println("PM10/2.5:");
    display.setCursor(200,y_offset+55);
    display.printf("%.1f", pm10);
    display.setCursor(200,y_offset+65);
    display.printf("%.1f", pm25);
    // Lux
    //display.setCursor(200,y_offset+80);
    //display.println("Lux:");
    //display.setCursor(200,y_offset+90);
    //display.printf("%.1f", sensor_readings.lux);
    // UV
    float uvi, uva, uvb;
    if (uv_active) {
      uvi = sensor_readings.uvi;
      uva = sensor_readings.uva;
      uvb = sensor_readings.uvb;
    } else if (!isnan(sensors_a4cf1211c3e4.uvi) || !isnan(sensors_a4cf1211c3e4.uva) || !isnan(sensors_a4cf1211c3e4.uvb)) {
      uvi = sensors_a4cf1211c3e4.uvi;
      uva = sensors_a4cf1211c3e4.uva;
      uvb = sensors_a4cf1211c3e4.uvb;
    } else if (!isnan(sensors_246f28d1fa5c.uvi) || !isnan(sensors_246f28d1fa5c.uva) || !isnan(sensors_246f28d1fa5c.uvb)) {
      uvi = sensors_246f28d1fa5c.uvi;
      uva = sensors_246f28d1fa5c.uva;
      uvb = sensors_246f28d1fa5c.uvb;
    } else if (!isnan(sensors_246f28d1a080.uvi) || !isnan(sensors_246f28d1a080.uva) || !isnan(sensors_246f28d1a080.uvb)) {
      uvi = sensors_246f28d1a080.uvi;
      uva = sensors_246f28d1a080.uva;
      uvb = sensors_246f28d1a080.uvb;
    } else {
      uvi = NAN;
      uva = NAN;
      uvb = NAN;
    }
    display.setCursor(200,y_offset+80);
    display.println("UV I/A/B:");
    display.setCursor(200,y_offset+90);
    display.printf("%.1f", uvi);
    display.setCursor(200,y_offset+100);
    display.printf("%.1f", uva);
    display.setCursor(200,y_offset+110);
    display.printf("%.1f", uvb);


 float lux;
    if (light_active) {
      lux = sensor_readings.lux;
    } else if (!isnan(sensors_a4cf1211c3e4.lux)) {
      lux = sensors_a4cf1211c3e4.lux;
    } else if (!isnan(sensors_246f28d1fa5c.lux)) {
      lux = sensors_246f28d1fa5c.lux;
    } else if (!isnan(sensors_246f28d1a080.lux)) {
      lux = sensors_246f28d1a080.lux;
    } else {
      lux = NAN;
      
    }
    display.setCursor(200,y_offset+120);
    display.println("LUX");
    display.setCursor(200,y_offset+130);
    display.printf("%.1f", lux);
    


    // other nodes
    display.setFont(NULL);
    display.setCursor(0, y_offset+70);
    if (!ota.getMAC().equals("246f28d1fa5c") && millis() - sensors_246f28d1fa5c.lastUpdate < 15*60*1000) {
      display.printf("246f28d1fa5c: %4.1f %4.1f %6.1f\n", sensors_246f28d1fa5c.temperature, sensors_246f28d1fa5c.humidity, sensors_246f28d1fa5c.pressure);
    }
    if (!ota.getMAC().equals("a4cf1211c3e4") && millis() - sensors_a4cf1211c3e4.lastUpdate < 15*60*1000) {
      display.printf("a4cf1211c3e4: %4.1f %4.1f %6.1f\n", sensors_a4cf1211c3e4.temperature, sensors_a4cf1211c3e4.humidity, sensors_a4cf1211c3e4.pressure);
    }
    if (!ota.getMAC().equals("246f28d1a080") && millis() - sensors_246f28d1a080.lastUpdate < 15*60*1000) {
      display.printf("246f28d1a080: %4.1f %4.1f %6.1f\n", sensors_246f28d1a080.temperature, sensors_246f28d1a080.humidity, sensors_246f28d1a080.pressure);
    }
    if (!ota.getMAC().equals("246f28d1eff4") && millis() - sensors_246f28d1eff4.lastUpdate < 15*60*1000) {
      display.printf("246f28d1eff4: %4.1f %4.1f %6.1f\n", sensors_246f28d1eff4.temperature, sensors_246f28d1eff4.humidity, sensors_246f28d1eff4.pressure);
    }

  }
  while (display.nextPage());

  display.powerOff();
}


void printValues() {
  if (bme280_active || bme680_active) {
    #define SEALEVELPRESSURE_HPA (1013.25)
    Serial.print("Temperature = ");
    Serial.print(sensor_readings.temperature);
    Serial.println(" *C");

    Serial.print("Pressure = ");
    Serial.print(sensor_readings.pressure);
    Serial.println(" hPa");

    Serial.print("Pressure [RAW] = ");
    Serial.print(sensor_readings.pressure_raw / 100.0F);
    Serial.println(" hPa");

    Serial.print("Humidity = ");
    Serial.print(sensor_readings.humidity);
    Serial.println(" %");
  }

  if (bme680_active) {
    Serial.print("VOC = ");
    Serial.print(sensor_readings.voc / 1000.0F);
    Serial.println(" kOhm");
  }

  Serial.println();

  if (uv_active) {
    Serial.print("UV Index reading: "); Serial.println(sensor_readings.uvi);
    Serial.print("Raw UVA reading:  "); Serial.println(sensor_readings.uva);
    Serial.print("Raw UVB reading:  "); Serial.println(sensor_readings.uvb);
    Serial.println();
  }

  if (sds_active) {
    Serial.print("PM2.5 = ");
    Serial.print(sensor_readings.pm25);
    Serial.print(", PM10 = ");
    Serial.println(sensor_readings.pm10);
  }

  Serial.print("Battery: "); Serial.println(sensor_readings.battery);

  Serial.print("Free HEAP: "); Serial.println(ESP.getFreeHeap());

}

void sendValues() {
    /* send values MQTT */
  if (bme280_active || bme680_active) {
    String topic_temperature = String("thomas/sensor/") + ota.getMAC() + String("/temperature");
    String topic_humidity = String("thomas/sensor/") + ota.getMAC() + String("/humidity");
    String topic_pressure = String("thomas/sensor/") + ota.getMAC() + String("/pressure");
    mqtt.publish(topic_temperature.c_str(), sensor_readings.temperature, "%.2f");
    delay(10);
    mqtt.publish(topic_humidity.c_str(), sensor_readings.humidity, "%.2f");
    delay(10);
    mqtt.publish(topic_pressure.c_str(), sensor_readings.pressure, "%.2f");
    delay(10);
  }
  if (bme680_active) {
    String topic_voc = String("thomas/sensor/") + ota.getMAC() + String("/voc");
    mqtt.publish(topic_voc.c_str(), sensor_readings.voc / 1000.0F, "%.2f");
    delay(10);
  }
  if (!bme280_active && !bme680_active) {
    String topic_temperature = String("thomas/sensor/") + ota.getMAC() + String("/temperature");
    float esp32_temperature = (temprature_sens_read() - 32) / 1.8;
    char temperature[8]; sprintf(temperature, "%.2f", esp32_temperature-29.40);
    mqtt.publish(topic_temperature.c_str(), temperature, strlen(temperature));
    delay(10);
  }

  if (uv_active) {
    String topic_uvi = String("thomas/sensor/") + ota.getMAC() + String("/uvi");
    String topic_uva = String("thomas/sensor/") + ota.getMAC() + String("/uva");
    String topic_uvb = String("thomas/sensor/") + ota.getMAC() + String("/uvb");
    mqtt.publish(topic_uvi.c_str(), sensor_readings.uvi, "%.2f");
    mqtt.publish(topic_uva.c_str(), sensor_readings.uva, "%.2f");
    mqtt.publish(topic_uvb.c_str(), sensor_readings.uvb, "%.2f");
    delay(10);
  }

  if (light_active) {
    String topic_lux = String("thomas/sensor/") + ota.getMAC() + String("/lux");
    mqtt.publish(topic_lux.c_str(), sensor_readings.lux, "%.2f");
    delay(10);
  }

  if (sds_active) {
    String topic_pm10 = String("thomas/sensor/") + ota.getMAC() + String("/pm10");
    String topic_pm25 = String("thomas/sensor/") + ota.getMAC() + String("/pm25");
    mqtt.publish(topic_pm10.c_str(), sensor_readings.pm10, "%.2f");
    mqtt.publish(topic_pm25.c_str(), sensor_readings.pm25, "%.2f");
    delay(10);
  }

  {
    String topic_battery = String("thomas/sensor/") + ota.getMAC() + String("/battery");
    mqtt.publish(topic_battery.c_str(), (sensor_readings.battery/4096.0)*2*3.42, "%.2f");
    delay(10);
  }

}

/**
 * \brief Setup function
 *
 * is run once on startup
 */
void setup()
{
  Serial.begin(115200);
  delay(10);

  ESP_LOGD(TAG, "setup hardware and sensors");

  // initialize LED digital pin as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(_VBAT, INPUT);
  analogReadResolution(12);
  analogSetAttenuation(ADC_11db);
  adcAttachPin(_VBAT);
  adcStart(_VBAT);

  // initialize e-paper display
  SPI.begin(18, 19, 23, TFT_CS);
  display.init();

  #define BME_SDA 21
  #define BME_SCL 22
  Wire.begin(BME_SDA, BME_SCL);
  if (bme280.begin()) {
    bme280_active = true;
  } else {
    ESP_LOGE(TAG, "Could not find a valid BME280 sensor, check wiring!");
  }
  if (bme680.begin()) {
    bme680_active = true;

    // Set up oversampling and filter initialization
    bme680.setTemperatureOversampling(BME680_OS_8X);
    bme680.setHumidityOversampling(BME680_OS_2X);
    bme680.setPressureOversampling(BME680_OS_4X);
    bme680.setIIRFilterSize(BME680_FILTER_SIZE_3);
    bme680.setGasHeater(320, 150); // 320*C for 150 ms
  } else {
    ESP_LOGE(TAG, "Could not find a valid BME680 sensor, check wiring!");
  }

  if (uv.begin()) {
    uv_active = true;

    uv.setIntegrationTime(VEML6075_100MS);  // Set the integration constant
    uv.setHighDynamic(true);  // Set the high dynamic mode
    uv.setForcedMode(false);
    // Set the calibration coefficients
    uv.setCoefficients(2.22, 1.33,  // UVA_A and UVA_B coefficients
                       2.95, 1.74,  // UVB_C and UVB_D coefficients
                       0.001461, 0.002591); // UVA and UVB responses
  } else {
    Serial.println("Failed to communicate with VEML6075 sensor, check wiring?");
  }

  if (lightMeter.begin()) {
    light_active = true;
  } else {
    Serial.println("Failed to communicate with BH1750 sensor, check wiring?");
  }

  sds.begin();

  FirmwareVersionResult sds_fw = sds.queryFirmwareVersion();
  if (sds_fw.isOk()) {
    sds_active = true;

    sds.setActiveReportingMode();  // ensures sensor is in 'active' reporting mode
    sds.setCustomWorkingPeriod(5); // sensor sends data every 3 minutes
  } else {
    Serial.println("Failed to communicate with SDS011 sensor, check wiring?");
  }

  display.clearScreen();
  display.refresh();

  ESP_LOGD(TAG, "displaying welcome screen");
  helloWorld();
  display.powerOff();

  ESP_LOGD(TAG, "connecting to WiFi");

  WiFi.setHostname("esp32-weatherstation");

  wifiMulti.addAP(WIFI_SSID, WIFI_PASSWD);
  wifiMulti.addAP(WIFI_SSID2, WIFI_PASSWD2);
  wifiMulti.addAP(WIFI_SSID3, WIFI_PASSWD3);

  for (int tries=0; wifiMulti.run() != WL_CONNECTED && tries < 10; tries++) {
    Serial.print(".");
    delay(500);
  }

  if(wifiMulti.run() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    displayIcoPartial(ico_wifi16, display.width()-20, y_offset+0, ico_wifi16_width, ico_wifi16_height);
  }

  ESP_LOGD(TAG, "trying to fetch over-the-air update");
  if (WiFi.status() == WL_CONNECTED) {
    ota.update();
  }

  WiFi.setSleep(true);

  ESP_LOGD(TAG, "connecting to MQTT");
  mqtt.begin();

  if (!ota.getMAC().equals("a4cf1211c3e4")) mqtt.subscribe("thomas/sensor/a4cf1211c3e4/#", receiveMqtt);
  if (!ota.getMAC().equals("246f28d1fa5c")) mqtt.subscribe("thomas/sensor/246f28d1fa5c/#", receiveMqtt);
  if (!ota.getMAC().equals("246f28d1a080")) mqtt.subscribe("thomas/sensor/246f28d1a080/#", receiveMqtt);
  if (!ota.getMAC().equals("246f28d1eff4")) mqtt.subscribe("thomas/sensor/246f28d1eff4/#", receiveMqtt);

  /* temp: publish version */
  delay(5000);
  String topic_version = String("thomas/sensor/") + ota.getMAC() + String("/version");
  const char* fw_version_str = String(FW_VERSION).c_str();
  mqtt.publish(topic_version.c_str(), fw_version_str, strlen(fw_version_str));

  if (WiFi.SSID() == "LNet") {
    station_height = 135;
  } else if (WiFi.SSID() == "Galaktisches Imperium") {
    station_height = 30;
  } else if (WiFi.SSID() == "nether.net") {
    station_height = 111;
  }

  ESP_LOGD(TAG, "setup done");
}

/**
 * \brief Arduino main loop
 */
void loop()
{
  /* Do an e-paper display refresh every 1 minutes */
  if (millis() - lastDisplayUpdate >= 1*60*1000) {
    lastDisplayUpdate = millis();

    /* Do a full refresh every hour */
    if (millis() - lastDisplayRefresh >= 60*60*1000) {
      lastDisplayRefresh = millis();
      display.clearScreen();
      display.refresh();
    }

    getSensorMeasurements();
    displayValues();
    printValues();
    sendValues();
  }

  if(wifiMulti.run() != WL_CONNECTED) {
    Serial.println("WiFi not connected!");
    delay(1000);
  }

  delay(2000);
}