here is something I am currently working on.
/*
https://github.com/G6EJD/ESP32-e-Paper-Weather-Display/blob/master/examples/Waveshare_4_2/Waveshare_4_2.ino
Stole some code from that guy.
*/
#include "MyBitmap.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <GxEPD2_BW.h>
#include <U8g2_for_Adafruit_GFX.h> // Select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
#include <Fonts/FreeMonoBold9pt7b.h> //https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts
#include <Fonts/FreeMono9pt7b.h>
#include <HardwareSerial.h>
#include <SimpleKalmanFilter.h>
#include "MHZ19.h"
#include <ESP32Time.h>
#include <SolarCalculator.h>
////
ESP32Time rtc;
MHZ19 myMHZ19;
GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEW042T2
Adafruit_BME680 bme( GPIO_NUM_15 );
WiFiClient wifiClient; // do the WiFi instantiation thing
PubSubClient MQTTclient( mqtt_server, mqtt_port, wifiClient );
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
////
#define evtStoreAirPressure ( 1 << 0 )
#define evtWaitForBME ( 1 << 1 )
#define evtParseMQTT ( 1 << 3 )
#define evtDisplayUpdate ( 1 << 4 )
#define evtDoBME ( 1 << 5 )
#define OneMinuteGroup ( evtDoBME )
EventGroupHandle_t eg;
//////
QueueHandle_t xQ_WindChillDewPoint;
QueueHandle_t xQ_eData;
struct stu_eData
{
float oTemperature = 0.0f;
float oHumidity = 0.0f;
float oPressure = 0.0f;
// for outside aqi???????????????????????? what does the RPi send an int or float???
float Temperature = 0.0f;
float Pressure = 0.0f;
float Humidity = 0.0f;
float IAQ = 0.0f; // Index Air Quality
float RM0 = 0.0f; // Remaining Moisture from sensor 0
//float PM2 = 0.0f; // particles in air
float WS = 0.0f; // wind speed
String WD = ""; // wind direction
float RF = 0.0f; // rainfall
//float WSV = 0.0f; // weather station volts
//float WSC = 0.0f; // weather station current
//float WSP = 0.0f; // weather station power
float WindChill = 0.0f; //windchill
float DewPoint = 0.0f; //dew point or dew index
int SunRiseHr = 0; // sunrise hour
int SunRiseMin = 0; //sunrise minute
int SunSetHr = 0; //sunset hour
int SunSetMin = 0; //sunset minute
int DuskHr = 0; //dusk
int DuskMin = 0; //dusk
int DawnHr = 0; // dawn
int DawnMin = 0; // dawn
int TransitHr = 0; // 'noon' time
int TransitMin = 0; // 'noon' time
double azimuth = 0.0f; // Sun's azimuth, in degrees
double elevation = 0.0f; // Sun's elevation, in degrees
float CO2 = 0.0f;
float PressureH = 0.0f;
float PressureL = 10000.0f;
} x_eData; // environmental data
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 100;
struct stu_message
{
char payload [payloadSize] = {'\0'};
String topic ;
} x_message;
////
const float oGasResistanceBaseLine = 149598.0f;
int mqttOK = 0;
volatile bool TimeSet = false;
const int BufferCount = 60;
float CollectionPressure[BufferCount] = {0.0f};
boolean LargeIcon = true, SmallIcon = false;
//bool CollectionDone = false;
#define Large 11 // For icon drawing, needs to be odd number for best effect
#define Small 5 // For icon drawing, needs to be odd number for best effect
enum alignment {LEFT, RIGHT, CENTER};
SemaphoreHandle_t sema_MQTT_KeepAlive;
SemaphoreHandle_t sema_PublishPM;
SemaphoreHandle_t sema_mqttOK;
SemaphoreHandle_t sema_CollectPressure;
////
//serial(2) = pin25 RX, pin26 TX
HardwareSerial co2Serial ( 2 );
////
void IRAM_ATTR onTimer()
{
BaseType_t xHigherPriorityTaskWoken;
xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
switch (event) {
break;
default: break;
}
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
x_message.topic = ""; //clear topic string buffer
x_message.topic = topic; //store new topic
int i = 0; // extract payload
for ( i; i < length; i++)
{
x_message.payload[i] = ((char)payload[i]);
}
x_message.payload[i] = '\0';
xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
void setup()
{
// hardware timer 4 set for one minute alarm
hw_timer_t * timer = NULL;
timer = timerBegin( 3, 80, true );
timerAttachInterrupt( timer, &onTimer, true );
timerAlarmWrite(timer, 60000000, true);
timerAlarmEnable(timer);
///
co2Serial.begin( 9600 , SERIAL_8N1, 25, 26 ); // pin25 RX, pin26 TX
x_eData.WD.reserve(50);
x_message.topic.reserve( payloadSize );
xQ_WindChillDewPoint = xQueueCreate( 1, sizeof(stu_eData) );
xQ_Message = xQueueCreate( 1, sizeof(stu_message) );
xQ_eData = xQueueCreate( 1, sizeof(stu_eData) ); // sends a queue copy of the structure
//
sema_PublishPM = xSemaphoreCreateBinary();
xSemaphoreGive( sema_PublishPM );
sema_mqttOK = xSemaphoreCreateBinary();
xSemaphoreGive( sema_mqttOK );
sema_CollectPressure = xSemaphoreCreateBinary();
xSemaphoreGive( sema_CollectPressure );
//
eg = xEventGroupCreate(); // get an event group handle
//
xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 5, NULL, 1 );
xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 5000, NULL, 6, NULL, 1 );
xTaskCreatePinnedToCore( DoTheBME680Thing, "DoTheBME280Thing", 20000, NULL, 5, NULL, 1);
xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 5000, NULL, 3, NULL, 1 );
xTaskCreatePinnedToCore( fDoTheDisplayThing, "fDoTheDisplayThing", 30000, NULL, 3, NULL, 1 );
xTaskCreatePinnedToCore( fGetCO2, "fGetCO2", 4500, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fParseDewPointWindChill, "fParseDewPointWindChill", 4500, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fSolarCalculations, "fSolarCalculations", 10000, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fStoreAirPressure, "fStoreAirPressure", 10000, NULL, 2, NULL, 1 );
} //void setup()
////
void fStoreAirPressure ( void *pvParemeters )
{
int cellCount = 0;
int Ticks = 0;
bool Filled = false;
const int ticksTrigger = 120; // triggered at 1 minute intervals
for (;;)
{
//triggered by BME which is triggered by the 1 minute hardware timer.
xEventGroupWaitBits (eg, evtStoreAirPressure, pdTRUE, pdTRUE, portMAX_DELAY );
xSemaphoreTake( sema_CollectPressure, portMAX_DELAY );
if ( !Filled )
{
CollectionPressure[cellCount] = x_eData.oPressure;
}
if ( Filled )
{
if ( Ticks == ticksTrigger )
{
//shift contents left and insert new value at the end
for ( int i = 0; i <= BufferCount - 2; i++ )
{
CollectionPressure[i] = CollectionPressure[i + 1];
}
}
CollectionPressure[BufferCount - 1] = x_eData.oPressure;
}
if ( x_eData.oPressure > x_eData.PressureH )
{
x_eData.PressureH = x_eData.oPressure;
}
if ( x_eData.oPressure < x_eData.PressureL )
{
x_eData.PressureL = x_eData.oPressure;
}
Ticks++;
if ( Ticks >= (ticksTrigger + 1) )
{
Ticks = 1;
cellCount++;
}
if ( cellCount == (BufferCount - 1) )
{
cellCount = 0;
if ( !Filled )
{
Filled = true;
}
}
xSemaphoreGive( sema_CollectPressure );
//
//log_i( " high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
} //for (;;)
vTaskDelete( NULL );
} //void fStoreAirPressure ( void *pvParemeters )
////
void fSolarCalculations ( void *pvParameters )
{
double sunrise; // Sunrise, in hours (UTC)
double transit; // Solar noon, in hours (UTC)
double sunset; // Sunset, in hours (UTC)
double dawn; // Civil dawn, in hours (UTC)
double dusk; // Civil dusk, in hours (UTC)
//double rt_ascension; // Sun's right ascension, in degrees
//double declination; // Sun's declination, in degrees
const float time_zone = -7.0f;
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = 1000; //delay for mS
int count = 3590;
int monthX = 1;
int dayX = 1;
for (;;)
{
if ( count % 60 == 0 )
{
if ( (rtc.getHour(true) >= 12) & (rtc.getHour(true) <= 23) )
{
dayX = 0;
} else {
dayX = 1;
}
calcSunriseSunset( rtc.getYear(), (rtc.getMonth() + monthX) , (rtc.getDay() + dayX), latitude, longitude, transit, sunrise, sunset ); // Calculate the times of sunrise, transit and sunset
sunrise += time_zone;
sunset += time_zone;
SolarTimeFormat( sunrise, 0 );
SolarTimeFormat( sunset, 1 );
calcCivilDawnDusk( rtc.getYear(), (rtc.getMonth() + monthX) , rtc.getDay(), latitude, longitude, transit, dawn, dusk); // Calculate the times of civil dawn and dusk (UTC)
transit += time_zone;
dawn += time_zone;
dusk += time_zone;
SolarTimeFormat( dawn, 2 );
SolarTimeFormat( dusk, 3 );
SolarTimeFormat( transit, 4 );
calcHorizontalCoordinates( rtc.getYear(), (rtc.getMonth() + monthX) , rtc.getDay(), rtc.getHour(true) , rtc.getMinute(), rtc.getSecond(), latitude, longitude, x_eData.azimuth, x_eData.elevation );
x_eData.azimuth = double(round(x_eData.azimuth * 100)) / 100; // Round to two decimal places
x_eData.elevation = double(round(x_eData.elevation * 100)) / 100;
if (count >= 3600 )
{
SolarTimeFormat( 0.0f, 5 ); // publish MQTT
//log_i( "Hour:%d Azimuth %f, elevation %f, transit %dhr %dmin, dawn %dhr %dmin, dusk %dhr %dmin", rtc.getHour(true), x_eData.azimuth, x_eData.elevation, x_eData.TransitHr, x_eData.TransitMin, x_eData.DawnHr, x_eData.DawnMin, x_eData.DuskHr, x_eData.DuskMin );
count = 0;
}
}
//log_i( " high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, xFrequency );
count++;
} //for (;;)
vTaskDelete( NULL );
} //void fSolarCalculations ( )
////
void SolarTimeFormat( double h, int i )
{
int hours = 0;
int minutes = 0;
if ( h != 0.0f )
{
int m = int(round(h * 60));
hours = (m / 60) % 24;
minutes = m % 60;
}
switch ( i )
{
case 0:
x_eData.SunRiseHr = hours;
x_eData.SunRiseMin = minutes;
break;
case 1:
x_eData.SunSetHr = hours;
x_eData.SunSetMin = minutes;
break;
case 2:
x_eData.DawnHr = hours;
x_eData.DawnMin = minutes;
break;
case 3:
x_eData.DuskHr = hours;
x_eData.DawnMin = minutes;
break;
case 4:
x_eData.TransitHr = hours;
x_eData.TransitMin = minutes;
break;
case 5:
String sTopic = "";
sTopic.reserve( 35 );
sTopic.concat( String(x_eData.SunRiseHr) + "," );
sTopic.concat( String(x_eData.SunRiseMin) + "," );
sTopic.concat( String(x_eData.SunSetHr) + "," );
sTopic.concat( String(x_eData.SunSetMin) + "," );
sTopic.concat( String(x_eData.DawnHr) + "," );
sTopic.concat( String(x_eData.DawnMin) + "," );
sTopic.concat( String(x_eData.TransitHr) + "," );
sTopic.concat( String(x_eData.TransitMin) );
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
MQTTclient.publish( topicSRSSDDT, sTopic.c_str() );
xSemaphoreGive( sema_MQTT_KeepAlive );
sTopic = "";
sTopic.concat( String(x_eData.azimuth) + "," + String(x_eData.elevation) );
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
MQTTclient.publish( topicAzEle, sTopic.c_str() );
xSemaphoreGive( sema_MQTT_KeepAlive );
sTopic = "";
break;
} // switch ( i ) {
} // void SolarTimeFormat( double h, int i )
/*
250-400ppm Normal background concentration in outdoor ambient air
400-1,000ppm Concentrations typical of occupied indoor spaces with good air exchange
1,000-2,000ppm Complaints of drowsiness and poor air.
2,000-5,000 ppm Headaches, sleepiness and stagnant, stale, stuffy air. Poor concentration, loss of attention, increased heart rate and slight nausea may also be present.
5,000 Workplace exposure limit (as 8-hour TWA) in most jurisdictions.
>40,000 ppm Exposure may lead to serious oxygen deprivation resulting in permanent brain damage, coma, even death.
*/
void fParseDewPointWindChill( void *pvParameters )
{
while ( !MQTTclient.connected() )
{
vTaskDelay( 250 );
}
struct stu_message px_message;
String sDewPoint = "";
String sWindChill = "";
sDewPoint.reserve( payloadSize );
sWindChill.reserve( payloadSize );
for (;;)
{
if ( xQueueReceive(xQ_WindChillDewPoint, &px_message, portMAX_DELAY) == pdTRUE )
{
sDewPoint = px_message.payload;
int commaIndex = sDewPoint.indexOf(',');
sWindChill.concat ( sDewPoint.substring(0, commaIndex) );
sDewPoint.remove( 0, (commaIndex + 1) );
x_eData.WindChill = sWindChill.toFloat();
x_eData.DewPoint = sDewPoint.toFloat();
sDewPoint = "";
sWindChill = "";
}
//log_i( " high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
}
vTaskDelete( NULL );
}
////
void fGetCO2 ( void *pvParameters )
{
uint64_t TimePastKalman = esp_timer_get_time();
myMHZ19.begin( co2Serial );
myMHZ19.autoCalibration();
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = 1000; //delay for mS
SimpleKalmanFilter KF_CO2( 1.0f, 1.0f, .01f );
for ( ;; )
{
KF_CO2.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f );
x_eData.CO2 = KF_CO2.updateEstimate( myMHZ19.getCO2() ); // apply simple Kalman filter
TimePastKalman = esp_timer_get_time();
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
MQTTclient.publish( topicCO2, String(round(x_eData.CO2)).c_str() );
xSemaphoreGive( sema_MQTT_KeepAlive );
// process wind chill and dew point
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, xFrequency );
//log_i( " high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
}
vTaskDelete( NULL );
} //void fMHZ19B ( void *pvParameters )
////
void fparseMQTT( void *pvParameters )
{
struct stu_message px_message;
for (;;)
{
if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
{
xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
mqttOK = 0;
xSemaphoreGive( sema_mqttOK );
if (px_message.topic == topicAQIndex )
{
}
if ( px_message.topic == topicOutsidePressure )
{
x_eData.oPressure = String(px_message.payload).toFloat();
}
if ( px_message.topic == topicOutsideHumidity )
{
x_eData.oHumidity = String(px_message.payload).toFloat();
}
if ( px_message.topic == topicOutsideTemperature )
{
x_eData.oTemperature = String(px_message.payload).toFloat();
}
if ( px_message.topic == topicRemainingMoisture_0 )
{
x_eData.RM0 = String(px_message.payload).toFloat();
}
if ( px_message.topic == topicWindSpeed )
{
x_eData.WS = String(px_message.payload).toFloat();
}
if ( px_message.topic == topicWindDirection )
{
x_eData.WD = "";
x_eData.WD = String(px_message.payload);
}
if ( px_message.topic == topicRainfall )
{
x_eData.RF = String(px_message.payload).toFloat();
}
// if ( px_message.topic == topicWSVolts )
// {
// x_eData.WSV = String(px_message.payload).toFloat();
// }
// if ( px_message.topic == topicWSCurrent )
// {
// x_eData.WSC = String(px_message.payload).toFloat();
// }
// if ( px_message.topic == topicWSPower )
// {
// x_eData.WSP = String(px_message.payload).toFloat();
// }
if ( px_message.topic == topicDPnWI )
{
xQueueSend( xQ_WindChillDewPoint, (void *) &px_message, 1 );
}
if ( String(px_message.topic) == topicOK )
{
if ( !TimeSet)
{
String temp = "";
temp.reserve(50);
temp.concat( String(px_message.payload[0]) );
temp.concat( String(px_message.payload[1]) );
temp.concat( String(px_message.payload[2]) );
temp.concat( String(px_message.payload[3]) );
int year = temp.toInt();
temp = "";
temp.concat( String(px_message.payload[5]) + String(px_message.payload[6]) );
int month = temp.toInt();
temp = "";
temp.concat(String(px_message.payload[8]) + String(px_message.payload[9]) );
int day = temp.toInt();
temp = "";
temp.concat( String(px_message.payload[11]) + String(px_message.payload[12]) );
int hour = temp.toInt();
temp = "";
temp.concat( String(px_message.payload[14]) + String(px_message.payload[15]) );
int min = temp.toInt();
rtc.setTime( 0, min, hour, day, month, year );
log_i( "rtc %s Year %d month %d day %d", rtc.getTime(), rtc.getYear(), (rtc.getMonth() + 1), rtc.getDay() );
TimeSet = true;
}
}
} //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
} //for(;;)
vTaskDelete( NULL );
} // void fparseMQTT( void *pvParameters )
////
void fDoTheDisplayThing( void * parameter )
{
struct stu_eData px_eData;
const char HelloWorld[] = "Hello World!";
//display.init();
//display.setFont(&FreeMonoBold9pt7b);
//display.setTextColor(GxEPD_BLACK);
int yIncrement = 18;
int CurrentY = 20;
int CurrentX = 5;
String temp1 = "";
temp1.reserve(10);
String temp2 = "";
temp2.reserve(10);
int boxSpacing = 80;
size_t item_size;
for (;;)
{
xEventGroupWaitBits (eg, evtDisplayUpdate, pdTRUE, pdTRUE, portMAX_DELAY );
//log_i( "pip" );
CurrentY = 20;
display.init();
//display.setFont(&FreeMonoBold9pt7b);
display.setFont(&FreeMono9pt7b);
//u8g2Fonts.setFont(u8g2_font_helvB08_tf);
display.setTextColor(GxEPD_BLACK);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE); // set the background to white (fill the buffer with value for white)
display.setCursor( CurrentX, CurrentY );
// first line
display.drawRect( CurrentX, CurrentY , 70, 55, GxEPD_BLACK);
display.drawBitmap( CurrentX + 10, CurrentY + 5, temperature_icon16x16, 16, 16, GxEPD_BLACK);
display.setCursor( CurrentX + 30, CurrentY + 15 );
display.print( "F" );
display.setCursor( CurrentX + 10, CurrentY + 40);
display.print( String(x_eData.oTemperature) );
display.drawRect( CurrentX + boxSpacing, CurrentY , 70, 55, GxEPD_BLACK);
display.setCursor( CurrentX + 90, CurrentY + 15 );
display.print( "R.H.");
display.setCursor( CurrentX + 90, CurrentY + 35 );
display.print( String((int)x_eData.oHumidity) );
// end of first line
if ( x_eData.SunRiseMin < 10 )
{
temp1.concat( "0" + String(x_eData.SunRiseMin) );
} else {
temp1.concat( String(x_eData.SunRiseMin) );
}
if ( x_eData.SunSetMin < 10 )
{
temp2.concat( "0" + String(x_eData.SunSetMin) );
} else {
temp2.concat( String(x_eData.SunSetMin) );
}
CurrentY += yIncrement;
CurrentY += yIncrement;
CurrentY += yIncrement;
display.setCursor( CurrentX, CurrentY );
display.print( "Wind: " );
CurrentY += yIncrement;
display.setCursor( CurrentX, CurrentY );
display.print( "Speed " + String(x_eData.WS) + "KPH, Dir " + String(x_eData.WD) + " Chill " + String(x_eData.WindChill) + "F" );
CurrentY += yIncrement;
CurrentY += yIncrement;
display.drawRect( CurrentX, CurrentY , 70, 55, GxEPD_BLACK);
addsun( 35, CurrentY + 30 , Small, SmallIcon );
display.setCursor( CurrentX + 5, CurrentY + 15 );
display.print( "0" + String(x_eData.SunRiseHr) + ":" + temp1 );
display.setCursor( CurrentX + 5, CurrentY + 50 );
display.print( String(x_eData.SunSetHr) + ":" + temp2 );
display.drawRect( CurrentX + boxSpacing, CurrentY , 70, 55, GxEPD_BLACK);
addraindrop(CurrentX + 110, CurrentY + 15, 7);
display.setCursor( CurrentX + 90, CurrentY + 35 );
display.print( String(x_eData.RF) );
display.setCursor( CurrentX + 100, CurrentY + 50 );
display.print( "mm" );
display.drawRect( CurrentX + (boxSpacing * 2 ), CurrentY , 70, 55, GxEPD_BLACK);
display.setCursor( CurrentX + 177, CurrentY + 15 );
display.print( "C02" );
display.setCursor( CurrentX + 165, CurrentY + 35 );
display.print( String(int(x_eData.CO2)) );
display.setCursor( CurrentX + 165, CurrentY + 50 );
display.print( "PPM" );
display.drawRect( CurrentX + (boxSpacing * 3 ), CurrentY , 70, 55, GxEPD_BLACK);
display.setCursor( CurrentX + 246, CurrentY + 15 );
display.print( "AQI" );
display.setCursor( CurrentX + 246, CurrentY + 35 );
display.print( String(int(x_eData.IAQ)) );
display.setCursor( CurrentX + 246, CurrentY + 50 );
display.print( "%" );
display.drawRect( CurrentX + (boxSpacing * 4 ), CurrentY , 70, 55, GxEPD_BLACK);
display.setCursor( CurrentX + 327, CurrentY + 15 );
display.print( "R.M." );
display.setCursor( CurrentX + 327, CurrentY + 35 );
display.print( String(int(x_eData.RM0)) + "%" );
//make graph
xSemaphoreTake( sema_CollectPressure, portMAX_DELAY );
CurrentY += yIncrement * 6;
display.setCursor( CurrentX, CurrentY); //set cursor position
//display.drawLine( CurrentX, CurrentY, CurrentX + 200, CurrentY, GxEPD_BLACK);
int BaseLine = CollectionPressure[0];
int offsetX = 0;
for ( int j = 0; j < BufferCount; j++ )
{
if ( CollectionPressure[j] != 0.0f )
{
int yAdj = (int)CollectionPressure[j] - BaseLine;
display.setCursor( CurrentX + offsetX, CurrentY + yAdj );
display.print( "-" );
offsetX += 5;
log_i( "pressure %f item %d", CollectionPressure[j], j );
}
}
xSemaphoreGive( sema_CollectPressure );
temp2 = "";
temp1 = "";
//
display.display(false); // full update
display.hibernate();
//log_i( "DoTheBME280Thing high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
} //for (;;)
vTaskDelete( NULL );
} //void fDoTheDisplayTHing( void * parameter )
////
void fmqttWatchDog( void * paramater )
{
int UpdateImeTrigger = 86400; //seconds in a day
int UpdateTimeInterval = 86300; // 1st time update in 100 counts
int maxNonMQTTresponse = 15;
for (;;)
{
vTaskDelay( 1000 );
if ( mqttOK >= maxNonMQTTresponse )
{
ESP.restart();
}
xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
mqttOK++;
xSemaphoreGive( sema_mqttOK );
UpdateTimeInterval++; // trigger new time get
if ( UpdateTimeInterval >= UpdateImeTrigger )
{
TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
UpdateTimeInterval = 0;
}
}
vTaskDelete( NULL );
}
////
float fCalulate_IAQ_Index( int gasResistance, float Humidity)
{
float hum_baseline = 40.0f;
float hum_weighting = 0.25f;
float gas_offset = 0.0f;
float hum_offset = 0.0f;
float hum_score = 0.0f;
float gas_score = 0.0f;
gas_offset = oGasResistanceBaseLine - float( gasResistance );
hum_offset = float( Humidity ) - hum_baseline;
// calculate hum_score as distance from hum_baseline
if ( hum_offset > 0.0f )
{
hum_score = 100.0f - hum_baseline - hum_offset;
hum_score /= ( 100.0f - hum_baseline );
hum_score *= ( hum_weighting * 100.0f );
} else {
hum_score = hum_baseline + hum_offset;
hum_score /= hum_baseline;
hum_score *= ( 100.0f - (hum_weighting * 100.0f) );
}
//calculate gas score as distance from baseline
if ( gas_offset > 0.0f )
{
gas_score = float( gasResistance ) / oGasResistanceBaseLine;
gas_score *= ( 100.0f - (hum_weighting * 100.0f ) );
} else {
gas_score = 100.0f - ( hum_weighting * 100.0f );
}
return ( hum_score + gas_score );
} //void fCalulate_IAQ_Index( int gasResistance, float Humidity):
////
void DoTheBME680Thing( void *pvParameters )
{
SPI.begin(); // initialize the SPI library
vTaskDelay( 10 );
if (!bme.begin()) {
log_i("Could not find a valid BME680 sensor, check wiring!");
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
//wait for a mqtt connection
while ( !MQTTclient.connected() )
{
vTaskDelay( 250 );
}
xEventGroupSetBits( eg, evtWaitForBME );
String bmeInfo = "";
bmeInfo.reserve( 100 );
for (;;)
{
xEventGroupWaitBits (eg, evtDoBME, pdTRUE, pdTRUE, portMAX_DELAY );
x_eData.Temperature = bme.readTemperature();
x_eData.Temperature = ( x_eData.Temperature * 1.8f ) + 32.0f; // (Celsius x 1.8) + 32
x_eData.Pressure = bme.readPressure();
x_eData.Pressure = x_eData.Pressure / 133.3223684f; //converts to mmHg
x_eData.Humidity = bme.readHumidity();
x_eData.IAQ = fCalulate_IAQ_Index( bme.readGas(), x_eData.Humidity );
//log_i( " temperature % f, Pressure % f, Humidity % f IAQ % f", x_eData.Temperature, x_eData.Pressure, x_eData.Humidity, x_eData.IAQ);
bmeInfo.concat( String(x_eData.Temperature, 2) );
bmeInfo.concat( "," );
bmeInfo.concat( String(x_eData.Pressure, 2) );
bmeInfo.concat( "," );
bmeInfo.concat( String(x_eData.Humidity, 2) );
bmeInfo.concat( "," );
bmeInfo.concat( String(x_eData.IAQ, 2) );
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
if ( MQTTclient.connected() )
{
MQTTclient.publish( topicInsideInfo, bmeInfo.c_str() );
}
xSemaphoreGive( sema_MQTT_KeepAlive );
xSemaphoreGive( sema_PublishPM ); // release publish of dust density
xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
mqttOK ++;
xSemaphoreGive( sema_mqttOK );
xEventGroupSetBits( eg, evtDisplayUpdate );
bmeInfo = ""; // empty the string buffer
findDewPointWithHumidity( x_eData.Humidity, x_eData.Temperature );
xEventGroupSetBits( eg, evtStoreAirPressure );
// log_i( "DoTheBME280Thing high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
}
vTaskDelete ( NULL );
}
////
/*
Important to not set vTaskDelay/vTaskDelayUntil to less then 10. Errors begin to develop with the MQTT and network connection.
makes the initial wifi/mqtt connection and works to keeps those connections open.
*/
void MQTTkeepalive( void *pvParameters )
{
sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = 250; //delay for ms
for (;;)
{
//check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
{
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
MQTTclient.loop();
xSemaphoreGive( sema_MQTT_KeepAlive );
}
else {
log_i( "MQTT keep alive found MQTT status % s WiFi status % s", String(wifiClient.connected()), String(WiFi.status()) );
if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
{
connectToWiFi();
}
connectToMQTT();
}
//log_i( " high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil( &xLastWakeTime, xFrequency );
}
vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
byte mac[5]; // create client ID from mac address
WiFi.macAddress(mac); // get mac address
String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
while ( !MQTTclient.connected() )
{
MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
vTaskDelay( 250 );
}
MQTTclient.setCallback ( mqttCallback );
MQTTclient.subscribe ( topicOK );
MQTTclient.subscribe ( topicRemainingMoisture_0 );
MQTTclient.subscribe ( topicWindSpeed );
MQTTclient.subscribe ( topicWindDirection );
MQTTclient.subscribe ( topicDPnWI );
MQTTclient.subscribe ( topicOutsideTemperature );
MQTTclient.subscribe ( topicOutsideHumidity );
MQTTclient.subscribe ( topicOutsidePressure );
} //void connectToMQTT()
void connectToWiFi()
{
int TryCount = 0;
while ( WiFi.status() != WL_CONNECTED )
{
TryCount++;
WiFi.disconnect();
WiFi.begin( SSID, PASSWORD );
vTaskDelay( 4000 );
if ( TryCount == 10 )
{
ESP.restart();
}
}
WiFi.onEvent( WiFiEvent );
}
////
float findDewPointWithHumidity( float humi, float temperature )
{
//Celcius
float ans = (temperature - (14.55 + 0.114 * temperature) * (1 - (0.01 * humi)) - pow(((2.5 + 0.007 * temperature) * (1 - (0.01 * humi))), 3) - (15.9 + 0.117 * temperature) * pow((1 - (0.01 * humi)), 14));
//log_i( "%f", ans );
return ans;
}
////
void addraindrop(int x, int y, int scale)
{
display.fillCircle(x, y, scale / 2, GxEPD_BLACK);
display.fillTriangle(x - scale / 2, y, x, y - scale * 1.2, x + scale / 2, y , GxEPD_BLACK);
x = x + scale * 1.6; y = y + scale / 3;
display.fillCircle(x, y, scale / 2, GxEPD_BLACK);
display.fillTriangle(x - scale / 2, y, x, y - scale * 1.2, x + scale / 2, y , GxEPD_BLACK);
}
////
void addsun(int x, int y, int scale, bool IconSize)
{
int linesize = 3;
if (IconSize == SmallIcon) linesize = 1;
display.fillRect(x - scale * 2, y, scale * 4, linesize, GxEPD_BLACK);
display.fillRect(x, y - scale * 2, linesize, scale * 4, GxEPD_BLACK);
display.drawLine(x - scale * 1.3, y - scale * 1.3, x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
display.drawLine(x - scale * 1.3, y + scale * 1.3, x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
if (IconSize == LargeIcon) {
display.drawLine(1 + x - scale * 1.3, y - scale * 1.3, 1 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
display.drawLine(2 + x - scale * 1.3, y - scale * 1.3, 2 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
display.drawLine(3 + x - scale * 1.3, y - scale * 1.3, 3 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
display.drawLine(1 + x - scale * 1.3, y + scale * 1.3, 1 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
display.drawLine(2 + x - scale * 1.3, y + scale * 1.3, 2 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
display.drawLine(3 + x - scale * 1.3, y + scale * 1.3, 3 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
}
display.fillCircle(x, y, scale * 1.3, GxEPD_WHITE);
display.fillCircle(x, y, scale, GxEPD_BLACK);
display.fillCircle(x, y, scale - linesize, GxEPD_WHITE);
}
////
void loop() { }