diff --git a/README.md b/README.md index ff916ca..184cf30 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ Serial (HardwareSerial2) SDS011 pin | ESP32 pin | Notes -----------|-----------|---------- -TXD | IO16 | -RXD | IO17 | +TXD | IO15 | (don't use IO16 together with PSRAM) +RXD | IO2 | (don't use IO17 together with PSRAM) GND | GND | 25µm | nc | 5V | VUSB | 5V diff --git a/platformio.ini b/platformio.ini index 2a40a72..59f4401 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,11 +18,11 @@ board = lolin_d32_pro board_build.f_cpu = 160000000L build_flags = - -DLOG_DEFAULT_LEVEL=ESP_LOG_VERBOSE - -DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -; -DBOARD_HAS_PSRAM -; -mfix-esp32-psram-cache-issue +; -DLOG_DEFAULT_LEVEL=ESP_LOG_VERBOSE +; -DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE +; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE + -DBOARD_HAS_PSRAM + -mfix-esp32-psram-cache-issue -DWIFI_SSID="\"${sysenv.WIFI_SSID}\"" -DWIFI_PASSWD="\"${sysenv.WIFI_PASSWD}\"" -DWIFI_SSID2="\"${sysenv.WIFI_SSID2}\"" @@ -47,7 +47,7 @@ lib_deps = ; SDS011 sensor Library ; Nova Fitness Sds dust sensors library https://github.com/lewapek/sds-dust-sensors-arduino-library.git -; ArduinoJSON + ArduinoJSON monitor_speed = 115200 upload_speed = 115200 diff --git a/src/main.cpp b/src/main.cpp index 2b2c113..d401d6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,8 @@ #include "network/XD0OTA.h" #include "network/XD0MQTT.h" +#include + #include "SensorHistory.h" #include "icons.h" @@ -42,6 +44,10 @@ uint8_t temprature_sens_read(); static const char* TAG = "MAIN"; + +#define TIME_TO_SLEEP 60 // seconds +constexpr unsigned int dhcp_interval = 60*60; + WiFiMulti wifiMulti; GxEPD2_BW display(GxEPD2_213_B72(/*CS=SS*/ TFT_CS, /*DC=*/ TFT_DC, /*RST=*/ TFT_RST, /*BUSY=*/ -1)); // GDEH0213B72 static constexpr uint8_t y_offset = 6; @@ -53,8 +59,22 @@ SdsDustSensor sds(Serial2); Adafruit_VEML6075 uv = Adafruit_VEML6075(); BH1750 lightMeter; -XD0OTA ota("esp32-weatherstation"); +constexpr unsigned int JSON_BUF_LEN = 512; +constexpr unsigned int JSON_CAPACITY = JSON_OBJECT_SIZE(16) + 0*JSON_ARRAY_SIZE(2) + 120; XD0MQTT mqtt; +XD0OTA ota("esp32-weatherstation"); + +struct __attribute__((packed)) network_t { + uint32_t ip; + uint32_t dns; + uint32_t gateway; + uint32_t subnet; + char ssid[64]; + char password[64]; + int32_t channel; + time_t last_dhcp; +}; +RTC_DATA_ATTR network_t network; struct __attribute__((packed)) sensor_readings_t { float temperature = NAN; // °C @@ -72,22 +92,143 @@ struct __attribute__((packed)) sensor_readings_t { float temperature_min = NAN; // °C int battery = 0; int8_t rssi = 0; - uint32_t lastUpdate = 0; + time_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; +RTC_DATA_ATTR time_t lastDisplayRefresh = 0; +struct __attribute__((packed)) sensors_active_t { + bool bme280 = false; + bool bme680 = false; + bool uv = false; + bool light = false; + bool sds = false; +}; +RTC_DATA_ATTR sensors_active_t sensors_active; float station_height = 0; +RTC_DATA_ATTR int bootCount = 0; + + +time_t getTimestamp() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec; +} + + +void poweroffDevices() { + display.powerOff(); + + if (sensors_active.bme680) { + bme680.setGasHeater(0, 0); + } + if (sensors_active.bme280) { + bme280.setSampling(Adafruit_BME280::MODE_SLEEP, + Adafruit_BME280::SAMPLING_X1, // temperature + Adafruit_BME280::SAMPLING_X1, // pressure + Adafruit_BME280::SAMPLING_X1, // humidity + Adafruit_BME280::FILTER_OFF ); + } + if (sensors_active.light) { + static constexpr byte BH1750_I2CADDR = 0x23; + Wire.beginTransmission(BH1750_I2CADDR); + Wire.write(BH1750_POWER_DOWN); + byte ack = Wire.endTransmission(); + } + if (sensors_active.uv) { + uv.shutdown(true); + + } +} + + +void gotoSleep(unsigned int sleep_time = TIME_TO_SLEEP) { + mqtt.end(); + WiFi.disconnect(); + WiFi.mode(WIFI_OFF); + + poweroffDevices(); + + //rtc_gpio_isolate(GPIO_NUM_12); + + esp_sleep_enable_timer_wakeup(sleep_time * 1000000LL); + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); + ESP_LOGI(TAG, "going to to sleep for %d seconds", sleep_time); + Serial.flush(); + esp_deep_sleep_start(); + delay(1); +} + +void wifiConnect() { + WiFi.persistent(false); + WiFi.setHostname("esp32-weatherstation"); + + wifiMulti.addAP(WIFI_SSID, WIFI_PASSWD); + wifiMulti.addAP(WIFI_SSID2, WIFI_PASSWD2); + wifiMulti.addAP(WIFI_SSID3, WIFI_PASSWD3); + + IPAddress ip = IPAddress(network.ip); + IPAddress dns = IPAddress(network.dns); + IPAddress subnet = IPAddress(network.subnet); + IPAddress gateway = IPAddress(network.gateway); + + Serial.println("millis(): " + String(millis())); + + ESP_LOGD(TAG, "previous dhcp: %lu s ago", getTimestamp() - network.last_dhcp); + + if ( ip != INADDR_NONE && dns != INADDR_NONE && gateway != INADDR_NONE && subnet != INADDR_NONE + && ((ip[0] == 192 && ip[1] == 168) || (ip[0] == 172 && ip[1] == 16)) + && strlen(network.ssid) > 0 && strlen(network.password) > 0 + && (getTimestamp() - network.last_dhcp < dhcp_interval) + ) { + + ESP_LOGD("WiFi", "STATIC IP"); + WiFi.config(ip, gateway, subnet, dns); + WiFi.begin(network.ssid, network.password, network.channel); + for (int tries=0; WiFi.status() != WL_CONNECTED && tries < 10; tries++) { + ESP_LOGD("WiFi", "."); + delay(500); + } + } else { + ESP_LOGD("WiFi", "DHCP"); + for (int tries=0; wifiMulti.run() != WL_CONNECTED && tries < 20; tries++) { + ESP_LOGD("WiFi", "."); + delay(500); + } + + network.ip = (uint32_t)WiFi.localIP(); + network.dns = (uint32_t)WiFi.dnsIP(); + network.gateway = (uint32_t)WiFi.gatewayIP(); + network.subnet = (uint32_t)WiFi.subnetMask(); + strncpy(network.ssid, WiFi.SSID().c_str(), 64); + strncpy(network.password, WiFi.psk().c_str(), 64); + network.channel = WiFi.channel(); + network.last_dhcp = getTimestamp(); + } + + Serial.println("millis(): " + String(millis())); + + if(WiFi.status() == WL_CONNECTED) { + ESP_LOGD("WiFi", "connected"); + //ESP_LOGD("WiFi", (WiFi.localIP().toString().c_str())); + + } else { + ESP_LOGE("WiFi", "could not connect to WiFi"); + ESP_LOGE(TAG, "restarting"); + ESP.restart(); + } +} + +bool obtain_time() { + ESP_LOGI(TAG, "syncing time"); + configTzTime("CET-1CEST,M3.5.0/2,M10.5.0/3", "de.pool.ntp.org"); + struct tm timeinfo; + return getLocalTime(&timeinfo, 5000); +} void helloWorld() { @@ -101,7 +242,11 @@ void helloWorld() // 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(); + if (display.epd2.hasFastPartialUpdate) { + display.setPartialWindow(0, 0, display.width(), display.height()); + } else { + display.setFullWindow(); + } display.firstPage(); do { @@ -139,20 +284,28 @@ void getTime(char* ptr, size_t maxsize, const char* format) { void getSensorMeasurements() { - if (bme280_active) { + if (sensors_active.bme280) { bme280.takeForcedMeasurement(); sensor_readings.temperature = bme280.readTemperature(); sensor_readings.humidity = bme280.readHumidity(); sensor_readings.pressure_raw = bme280.readPressure(); + ESP_LOGI(TAG, "Temperature : %8.2f °C", sensor_readings.temperature); + ESP_LOGI(TAG, "Pressure (Raw): %8.2f Pa", sensor_readings.pressure_raw); + ESP_LOGI(TAG, "Humidity : %8.2f %", sensor_readings.humidity); } - if (bme680_active) { + if (sensors_active.bme680) { + bme680.endReading(); // ToDo 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; + ESP_LOGI(TAG, "Temperature : %8.2f °C", sensor_readings.temperature); + ESP_LOGI(TAG, "Pressure (Raw): %8.2f Pa", sensor_readings.pressure_raw); + ESP_LOGI(TAG, "Humidity : %8.2f %", sensor_readings.humidity); + ESP_LOGI(TAG, "VOC : %5lu kOhm", sensor_readings.voc); } else { - Serial.println("Failed to perform reading :("); + ESP_LOGE(TAG, "Failed to perform BME680 reading :("); } } @@ -174,50 +327,62 @@ void getSensorMeasurements() { history_pressure.addValue(sensor_readings.pressure); - if (uv_active) { + if (sensors_active.uv) { sensor_readings.uvi = uv.readUVI(); sensor_readings.uva = uv.readUVA(); sensor_readings.uvb = uv.readUVB(); + ESP_LOGI(TAG, "UVI : %8.2f", sensor_readings.uvi); + ESP_LOGI(TAG, "UVA : %8.2f", sensor_readings.uva); + ESP_LOGI(TAG, "UVB : %8.2f", sensor_readings.uvb); } - if (light_active) { + if (sensors_active.light) { sensor_readings.lux = lightMeter.readLightLevel(); // auto-adjust sensitivity if (sensor_readings.lux < 0) { - Serial.println("Error reading light level"); + ESP_LOGE(TAG, "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")); + ESP_LOGD(TAG, "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")); + ESP_LOGD(TAG, "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")); + ESP_LOGD(TAG, "Setting MTReg to default value for normal light environment"); } } + ESP_LOGI(TAG, "Lux : %8.2f lx", sensor_readings.lux); } - if (sds_active) { + if (sensors_active.sds) { PmResult pm = sds.readPm(); if (pm.isOk()) { sensor_readings.pm10 = pm.pm10; sensor_readings.pm25 = pm.pm25; + ESP_LOGI(TAG, "PM10 : %8.2f µg/m³", sensor_readings.pm10); + ESP_LOGI(TAG, "PM2.5 : %8.2f µg/m³", sensor_readings.pm25); } } sensor_readings.battery = analogRead(_VBAT); sensor_readings.rssi = WiFi.RSSI(); - sensor_readings.lastUpdate = millis(); + ESP_LOGI(TAG, "RSSI : %5d dBm", sensor_readings.rssi); + ESP_LOGI(TAG, "Battery : %5d ", sensor_readings.battery); + ESP_LOGI(TAG, "Heap : %5lu", ESP.getFreeHeap()); + + sensor_readings.lastUpdate = getTimestamp(); } -void receiveMqtt(const char* topic, const char* data) { +void receiveMqtt(const char* topic, const char* data, int data_len) { sensor_readings_t* sensor = NULL; + ESP_LOGI(TAG, "received MQTT message on subscribed topic %s", topic); + if (strstr(topic, "thomas/sensor/a4cf1211c3e4") == topic) { sensor = &sensors_a4cf1211c3e4; } else if (strstr(topic, "thomas/sensor/246f28d1fa5c") == topic) { @@ -231,8 +396,32 @@ void receiveMqtt(const char* topic, const char* data) { char* topic_last = strrchr(topic, '/'); if (topic_last && sensor) { - if (strcmp("/temperature", topic_last) == 0) { + if (strcmp("/json", topic_last) == 0) { + StaticJsonDocument jsonDoc; + DeserializationError err = deserializeJson(jsonDoc, data, data_len); + if (err) { + ESP_LOGW(TAG, "Error parsing JSON, code: %s", err.c_str()); + } else { + // got json + if (jsonDoc.containsKey("temperature")) sensor->temperature = jsonDoc["temperature"].as(); + if (jsonDoc.containsKey("humidity")) sensor->humidity = jsonDoc["humidity"].as(); + if (jsonDoc.containsKey("pressure")) sensor->pressure = jsonDoc["pressure"].as(); + if (jsonDoc.containsKey("voc")) sensor->voc = jsonDoc["voc"].as(); + if (jsonDoc.containsKey("lux")) sensor->lux = jsonDoc["lux"].as(); + if (jsonDoc.containsKey("uvi")) sensor->uvi = jsonDoc["uvi"].as(); + if (jsonDoc.containsKey("uva")) sensor->uva = jsonDoc["uva"].as(); + if (jsonDoc.containsKey("uvb")) sensor->uvb = jsonDoc["uvb"].as(); + if (jsonDoc.containsKey("pm10")) sensor->pm10 = jsonDoc["pm10"].as(); + if (jsonDoc.containsKey("pm2.5")) sensor->pm25 = jsonDoc["pm2.5"].as(); + if (jsonDoc.containsKey("voltage")) sensor->battery = jsonDoc["voltage"].as(); + if (jsonDoc.containsKey("rssi")) sensor->rssi = jsonDoc["rssi"].as(); + if (jsonDoc.containsKey("timestamp")) sensor->lastUpdate = jsonDoc["timestamp"].as(); + ESP_LOGI(TAG, "got new values from %s, timestamp: %lu", topic, sensor->lastUpdate); + ESP_LOGI(TAG, "%lu seconds ago", topic, getTimestamp() - sensor->lastUpdate); + } + } else if (strcmp("/temperature", topic_last) == 0) { sensor->temperature = atof(data); + sensor->lastUpdate = getTimestamp(); } else if (strcmp("/humidity", topic_last) == 0) { sensor->humidity = atof(data); } else if (strcmp("/pressure", topic_last) == 0) { @@ -252,7 +441,6 @@ void receiveMqtt(const char* topic, const char* data) { } else if (strcmp("/voc", topic_last) == 0) { sensor->voc = atof(data); } - sensor->lastUpdate = millis(); } } @@ -267,6 +455,7 @@ void displayValues() { char timeStr[40]; getTime(timeStr, sizeof(timeStr), "%d. %b %Y %H:%M:%S"); + ESP_LOGD(TAG, "displayValues()"); if (display.epd2.hasFastPartialUpdate) { display.setPartialWindow(0, 0, display.width(), display.height()); @@ -354,7 +543,7 @@ void displayValues() { display.printf("%.1f k\xe9", sensor_readings.voc / 1000.0F); // PM float pm10, pm25; - if (sds_active) { + if (sensors_active.sds) { pm10 = sensor_readings.pm10; pm25 = sensor_readings.pm25; } else if (!isnan(sensors_a4cf1211c3e4.pm10) || !isnan(sensors_a4cf1211c3e4.pm25)) { @@ -378,7 +567,7 @@ void displayValues() { display.printf("%.1f", pm25); // Lux float lux; - if (light_active) { + if (sensors_active.light) { lux = sensor_readings.lux; } else if (!isnan(sensors_a4cf1211c3e4.lux)) { lux = sensors_a4cf1211c3e4.lux; @@ -395,7 +584,7 @@ void displayValues() { display.printf("%.1f lx", lux); // UV float uvi, uva, uvb; - if (uv_active) { + if (sensors_active.uv) { uvi = sensor_readings.uvi; uva = sensor_readings.uva; uvb = sensor_readings.uvb; @@ -428,16 +617,16 @@ void displayValues() { // other nodes display.setFont(NULL); display.setCursor(0, y_offset+70); - if (!ota.getMAC().equals("246f28d1fa5c") && millis() - sensors_246f28d1fa5c.lastUpdate < 15*60*1000) { + if (!ota.getMAC().equals("246f28d1fa5c") && getTimestamp() - sensors_246f28d1fa5c.lastUpdate < 15*60) { 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) { + if (!ota.getMAC().equals("a4cf1211c3e4") && getTimestamp() - sensors_a4cf1211c3e4.lastUpdate < 15*60) { 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) { + if (!ota.getMAC().equals("246f28d1a080") && getTimestamp() - sensors_246f28d1a080.lastUpdate < 15*60) { 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) { + if (!ota.getMAC().equals("246f28d1eff4") && getTimestamp() - sensors_246f28d1eff4.lastUpdate < 15*60) { display.printf("246f28d1eff4: %4.1f %4.1f %6.1f\n", sensors_246f28d1eff4.temperature, sensors_246f28d1eff4.humidity, sensors_246f28d1eff4.pressure); } @@ -448,58 +637,45 @@ void displayValues() { } -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("RSSI: "); Serial.println(sensor_readings.rssi); - Serial.print("Battery: "); Serial.println(sensor_readings.battery); - - Serial.print("Free HEAP: "); Serial.println(ESP.getFreeHeap()); - -} - void sendValues() { + for (int tries=0; mqtt.isConnected() == false && tries < 10; tries++) { + ESP_LOGD(TAG, "waiting for mqtt connection"); + delay(300); + } + + /* send values MQTT JSON */ + char buf[JSON_BUF_LEN]; + StaticJsonDocument jsonDoc; + if (sensors_active.bme280 || sensors_active.bme680) { + jsonDoc["temperature"] = sensor_readings.temperature; + jsonDoc["humidity"] = sensor_readings.humidity; + jsonDoc["pressure"] = sensor_readings.pressure; + } + if (sensors_active.bme680) { + jsonDoc["voc"] = sensor_readings.voc; + } + if (sensors_active.light) { + jsonDoc["lux"] = sensor_readings.lux; + } + if (sensors_active.uv) { + jsonDoc["uvi"] = sensor_readings.uvi; + jsonDoc["uva"] = sensor_readings.uva; + jsonDoc["uvb"] = sensor_readings.uvb; + } + if (sensors_active.sds) { + jsonDoc["pm10"] = sensor_readings.pm10; + jsonDoc["pm2.5"] = sensor_readings.pm25; + } + jsonDoc["voltage"] = sensor_readings.battery; + jsonDoc["rssi"] = sensor_readings.rssi; + jsonDoc["timestamp"] = sensor_readings.lastUpdate; + serializeJson(jsonDoc, buf, JSON_BUF_LEN); + String topic_json = String("thomas/sensor/") + ota.getMAC() + String("/json"); + mqtt.publish(topic_json.c_str(), buf, strlen(buf), 1, 1); + delay(10); + /* send values MQTT */ - if (bme280_active || bme680_active) { + if (sensors_active.bme280 || sensors_active.bme680) { 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"); @@ -510,20 +686,13 @@ void sendValues() { mqtt.publishf(topic_pressure.c_str(), "%.2f", sensor_readings.pressure); delay(10); } - if (bme680_active) { + if (sensors_active.bme680) { String topic_voc = String("thomas/sensor/") + ota.getMAC() + String("/voc"); mqtt.publishf(topic_voc.c_str(), "%.2f", sensor_readings.voc / 1000.0F); 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) { + if (sensors_active.uv) { 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"); @@ -533,13 +702,13 @@ void sendValues() { delay(10); } - if (light_active) { + if (sensors_active.light) { String topic_lux = String("thomas/sensor/") + ota.getMAC() + String("/lux"); mqtt.publishf(topic_lux.c_str(), "%.2f", sensor_readings.lux); delay(10); } - if (sds_active) { + if (sensors_active.sds) { String topic_pm10 = String("thomas/sensor/") + ota.getMAC() + String("/pm10"); String topic_pm25 = String("thomas/sensor/") + ota.getMAC() + String("/pm25"); mqtt.publishf(topic_pm10.c_str(), "%.2f", sensor_readings.pm10); @@ -569,7 +738,12 @@ void sendValues() { void setup() { Serial.begin(115200); - delay(10); + Serial2.begin(9600, SERIAL_8N1, /*rx*/15, /*tx*/2); // IMPORTANT: don't run with default pins 16, 17 as they are connected to PSRAM on boards that ship with it + + esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); + ++bootCount; + ESP_LOGI(TAG, "Boot number: %d", bootCount); + Serial.println("millis(): " + String(millis())); ESP_LOGD(TAG, "setup hardware and sensors"); @@ -583,20 +757,21 @@ void setup() adcAttachPin(_VBAT); adcStart(_VBAT); - // initialize e-paper display - SPI.begin(18, 19, 23, TFT_CS); // MISO is not connected to TFT_MISO! - display.init(); + Serial.println("millis(): " + String(millis())); #define BME_SDA 21 #define BME_SCL 22 Wire.begin(BME_SDA, BME_SCL); if (bme280.begin()) { - bme280_active = true; + sensors_active.bme280 = true; } else { ESP_LOGE(TAG, "Could not find a valid BME280 sensor, check wiring!"); } + + Serial.println("millis(): " + String(millis())); + if (bme680.begin()) { - bme680_active = true; + sensors_active.bme680 = true; // Set up oversampling and filter initialization bme680.setTemperatureOversampling(BME680_OS_8X); @@ -604,12 +779,15 @@ void setup() bme680.setPressureOversampling(BME680_OS_4X); bme680.setIIRFilterSize(BME680_FILTER_SIZE_3); bme680.setGasHeater(320, 150); // 320*C for 150 ms + bme680.beginReading(); } else { ESP_LOGE(TAG, "Could not find a valid BME680 sensor, check wiring!"); } + Serial.println("millis(): " + String(millis())); + if (uv.begin()) { - uv_active = true; + sensors_active.uv = true; uv.setIntegrationTime(VEML6075_100MS); // Set the integration constant uv.setHighDynamic(true); // Set the high dynamic mode @@ -619,58 +797,72 @@ void setup() 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?"); + ESP_LOGW(TAG, "Failed to communicate with VEML6075 sensor, check wiring?"); } + Serial.println("millis(): " + String(millis())); + if (lightMeter.begin()) { - light_active = true; + sensors_active.light = true; + lightMeter.setMTreg((byte) BH1750_DEFAULT_MTREG); } else { - Serial.println("Failed to communicate with BH1750 sensor, check wiring?"); + ESP_LOGW(TAG, "Failed to communicate with BH1750 sensor, check wiring?"); } - sds.begin(); + Serial.println("millis(): " + String(millis())); - FirmwareVersionResult sds_fw = sds.queryFirmwareVersion(); - if (sds_fw.isOk()) { - sds_active = true; + //sds.begin(); // don't call begin, only messes with Serial - 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?"); - } + Serial.println("millis(): " + String(millis())); - //display.clearScreen(); - //display.refresh(); + if (wakeup_reason == ESP_SLEEP_WAKEUP_UNDEFINED || bootCount == 1) { + FirmwareVersionResult sds_fw = sds.queryFirmwareVersion(); + if (sds_fw.isOk()) { + sensors_active.sds = true; - ESP_LOGD(TAG, "displaying welcome screen"); - helloWorld(); - display.powerOff(); + sds.setActiveReportingMode(); // ensures sensor is in 'active' reporting mode + sds.setCustomWorkingPeriod(5); // sensor sends data every 3 minutes + } else { + ESP_LOGW(TAG, "Failed to communicate with SDS011 sensor, check wiring?"); + } + } - ESP_LOGD(TAG, "connecting to WiFi"); + Serial.println("millis(): " + String(millis())); - WiFi.setHostname("esp32-weatherstation"); + // initialize e-paper display + SPI.begin(18, 19, 23, TFT_CS); // MISO is not connected to TFT_MISO! + display.init(0, false, false); + display.setRotation(1); - wifiMulti.addAP(WIFI_SSID, WIFI_PASSWD); - wifiMulti.addAP(WIFI_SSID2, WIFI_PASSWD2); - wifiMulti.addAP(WIFI_SSID3, WIFI_PASSWD3); + Serial.println("millis(): " + String(millis())); - for (int tries=0; wifiMulti.run() != WL_CONNECTED && tries < 10; tries++) { - Serial.print("."); - delay(500); + if (wakeup_reason == ESP_SLEEP_WAKEUP_UNDEFINED || bootCount == 1) { + // wakeup not caused by deep sleep + display.clearScreen(); + display.refresh(); + lastDisplayRefresh = getTimestamp(); + helloWorld(); + display.powerOff(); + } else { + // wakeup by deep sleep +// displayValues(); } - 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, "connecting to WiFi"); + Serial.println("millis(): " + String(millis())); + + wifiConnect(); - ESP_LOGD(TAG, "trying to fetch over-the-air update"); - if (WiFi.status() == WL_CONNECTED) { - ota.update(); + WiFi.waitForConnectResult(); + displayIcoPartial(ico_wifi16, display.width()-20, y_offset+0, ico_wifi16_width, ico_wifi16_height); + + if (wakeup_reason == ESP_SLEEP_WAKEUP_UNDEFINED || bootCount == 1) { + // wakeup not caused by deep sleep + obtain_time(); + ESP_LOGD(TAG, "trying to fetch over-the-air update"); + if (WiFi.status() == WL_CONNECTED) { + ota.update(); + } } WiFi.setSleep(true); @@ -678,13 +870,12 @@ void setup() 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); + if (!ota.getMAC().equals("a4cf1211c3e4")) mqtt.subscribe("thomas/sensor/a4cf1211c3e4/json", receiveMqtt); + if (!ota.getMAC().equals("246f28d1fa5c")) mqtt.subscribe("thomas/sensor/246f28d1fa5c/json", receiveMqtt); + if (!ota.getMAC().equals("246f28d1a080")) mqtt.subscribe("thomas/sensor/246f28d1a080/json", receiveMqtt); + if (!ota.getMAC().equals("246f28d1eff4")) mqtt.subscribe("thomas/sensor/246f28d1eff4/json", 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)); @@ -705,27 +896,26 @@ void setup() */ void loop() { - /* Do an e-paper display refresh every 1 minutes */ - if (millis() - lastDisplayUpdate >= 1*60*1000) { - lastDisplayUpdate = millis(); +/* if(wifiMulti.run() != WL_CONNECTED) { + Serial.println("WiFi not connected!"); + delay(1000); + }*/ /* Do a full refresh every hour */ - if (millis() - lastDisplayRefresh >= 60*60*1000) { - lastDisplayRefresh = millis(); - display.clearScreen(); - display.refresh(); - } - - getSensorMeasurements(); - displayValues(); - printValues(); - sendValues(); + if (getTimestamp() - lastDisplayRefresh >= 60*60) { + lastDisplayRefresh = getTimestamp(); + display.clearScreen(); + display.refresh(); } - if(wifiMulti.run() != WL_CONNECTED) { - Serial.println("WiFi not connected!"); - delay(1000); - } + getSensorMeasurements(); + sendValues(); + delay(1); + displayValues(); + + int runtime = millis()/1000; + if (runtime < 0 || runtime >= TIME_TO_SLEEP) runtime = 0; + gotoSleep(TIME_TO_SLEEP - runtime); delay(2000); } diff --git a/src/network/XD0MQTT.cpp b/src/network/XD0MQTT.cpp index 8b1e7e1..7ba6a03 100644 --- a/src/network/XD0MQTT.cpp +++ b/src/network/XD0MQTT.cpp @@ -60,13 +60,15 @@ esp_err_t XD0MQTT::mqtt_event_handler_cb(esp_mqtt_event_handle_t event) ESP_LOGI(TAG, "TOPIC=%.*s\r\n", event->topic_len, event->topic); ESP_LOGI(TAG, "DATA=%.*s\r\n", event->data_len, event->data); for (const auto &subscription : subscriptions_) { + char topic[128]; + strncpy(topic, event->topic, event->topic_len); topic[event->topic_len] = '\0'; // literal match - if (strcmp(event->topic, subscription.topic) == 0) { - subscription.cb(event->topic, event->data); + if (event->topic_len == strlen(subscription.topic) && strncmp(event->topic, subscription.topic, event->topic_len) == 0) { + subscription.cb(topic, event->data, event->data_len); // '#' wildcard } else if (subscription.topic[strlen(subscription.topic)-1] == '#'){ - if (strncmp(event->topic, subscription.topic, strlen(subscription.topic)-1) == 0) { - subscription.cb(event->topic, event->data); + if (event->topic_len >= strlen(subscription.topic) && strncmp(event->topic, subscription.topic, strlen(subscription.topic)-1) == 0) { + subscription.cb(topic, event->data, event->data_len); } } // ToDo: '+' wildcard @@ -106,10 +108,10 @@ bool XD0MQTT::begin(void) { mqtt_cfg.password = MQTT_PASSWORD; mqtt_cfg.user_context = (void*)this; - ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); - Serial.printf("BROKER URI: %s\n", MQTT_BROKER_URI); - Serial.printf("MQTT USERNAME: %s\n", MQTT_USERNAME); - Serial.printf("MQTT PASSWORD: %s\n", MQTT_PASSWORD); + ESP_LOGD(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "BROKER URI: %s\n", MQTT_BROKER_URI); + ESP_LOGI(TAG, "MQTT USERNAME: %s\n", MQTT_USERNAME); + ESP_LOGI(TAG, "MQTT PASSWORD: %s\n", MQTT_PASSWORD); client = esp_mqtt_client_init(&mqtt_cfg); //esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); esp_mqtt_client_start(client); @@ -128,6 +130,10 @@ void XD0MQTT::end(void) { esp_mqtt_client_destroy(client); } +bool XD0MQTT::isConnected(void) { + return connected; +} + bool XD0MQTT::publish(const char* topic, const char* data, int len, int qos, int retain) { int msg_id = esp_mqtt_client_publish(client, topic, data, len, qos, retain); ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); diff --git a/src/network/XD0MQTT.h b/src/network/XD0MQTT.h index a4699fd..43837da 100644 --- a/src/network/XD0MQTT.h +++ b/src/network/XD0MQTT.h @@ -17,7 +17,7 @@ #include "mqtt_client.h" -typedef std::function cb_t; +typedef std::function cb_t; struct subscription_t { const char* topic; @@ -37,9 +37,10 @@ class XD0MQTT { bool subscribe(const char* topic, const cb_t &cb, int qos=1); bool unsubscribe(const char* topic); esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event); - bool connected = false; + bool isConnected(void); std::vector subscriptions_; private: + bool connected = false; // openssl s_client -showcerts -connect home.xd0.de:8883 /dev/null|openssl x509 -outform PEM >mqtt_xd0.de.pem const char* rootCACertificate = \ "-----BEGIN CERTIFICATE-----\n" \ diff --git a/src/network/XD0OTA.cpp b/src/network/XD0OTA.cpp index 5793034..32ab5f6 100644 --- a/src/network/XD0OTA.cpp +++ b/src/network/XD0OTA.cpp @@ -16,23 +16,21 @@ XD0OTA::XD0OTA(String deviceName) : deviceName{deviceName} {} void XD0OTA::setClock() { configTime(0, 0, "de.pool.ntp.org", "time.nist.gov"); // UTC - Serial.print(F("Waiting for NTP time sync: ")); + ESP_LOGI(TAG, "Waiting for NTP time sync: "); time_t now = time(nullptr); int tries = 0; while (now < 8 * 3600 * 2) { yield(); delay(500); - Serial.print(F(".")); + ESP_LOGI(TAG, "."); now = time(nullptr); tries++; if (tries>15) return; } - Serial.println(F("")); struct tm timeinfo; gmtime_r(&now, &timeinfo); - Serial.print(F("Current time: ")); - Serial.print(asctime(&timeinfo)); + ESP_LOGI(TAG, "Current time: %s", asctime(&timeinfo)); // setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1); // tzset(); @@ -57,7 +55,7 @@ String XD0OTA::getUpdateURL(String file, String extension) { } void XD0OTA::update(void) { - setClock(); + //setClock(); // try device specific image first String fwURL = getUpdateURL(getMAC(), ".bin");