#include // display mapping for Arduino MEGA // BUSY -> 7, RST -> 9, DC -> 8, CS-> 53, CLK -> 52, DIN -> 51 #include #include #include "FreeSansBold11pt7b.h" #include "FreeSans22pt7b.h" #include #include #include #include /**************************** BME280 setup **********************************/ BME280I2C::Settings settings( BME280::OSR_X1, BME280::OSR_X1, BME280::OSR_X1, BME280::Mode_Forced, BME280::StandbyTime_1000ms, BME280::Filter_4, BME280::SpiEnable_False, 0x76 // I2C address. I2C specific. ); BME280I2C bme(settings); /**************************** Serial setup **********************************/ #define BAUDRATE 115200 /**************************** Screen setup **********************************/ #define MAX_DISPAY_BUFFER_SIZE 32 #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8)) GxEPD2_BW display(GxEPD2_290(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); /*************************** Global variables *******************************/ struct data { float temp; float hum; int rhum; float pres; unsigned int rpres; int16_t co2; }; volatile static struct data dataset; static struct lp8_t sensor; char incoming[40]; char ssid[16]; char ip[16]; static byte bmeerr = 0; /******************************* Setup routine ******************************/ void setup() { ADCSRA = 0; //turn off adc to save power as it is not needed pinMode(13, OUTPUT); digitalWrite(13, LOW); //turn off led Serial.begin(BAUDRATE); Serial2.begin(BAUDRATE); Wire.begin(); Serial.print(F("\r\n***Enviro data monitor***\r\n")); //start co2 module init_co2_module(); display.init(); //init display delay(100); layoutScreen(); //print static parts of screen //make sure we're always in 24hr mode, invert to change to 12h switch (RTC.status(DS3231_12H)) { case DS3231_OFF: Serial.println(F("RTC in 24h mode")); break; case DS3231_ON: Serial.println(F("RTC in 12h mode, fixing")); //should not occur RTC.control(DS3231_12H, DS3231_OFF); break; default: break; } Serial.println(F("Waiting for ESP to boot")); bool n; static byte tries = 0; Serial.print("["); while(1) { delay(1000); Serial.print("="); n = readNetInfo(); if(n == true) { break; } else { tries++; if(tries >= 33) { Serial.print("X"); Serial.println(); Serial.println(F("Could not read connection info, likely no network connection")); display.setFont(&TomThumb); display.setTextColor(GxEPD_BLACK); uint16_t x = 10; uint16_t y = 277; display.setPartialWindow(0, 270, display.width(), 20); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x,y); display.print("Network error"); } while (display.nextPage()); tries = 0; break; } } } static byte retries = 0; bool t; while(1){ //sync time with network delay(5000); t = syncTime(); if(t == true) { break; } else { retries++; if(retries > 4){ //break after 5 tries Serial.println(F("Could not sync time\n")); break; } } } retries = 0; printRTCTimeToScreen(); //print time to screen printRTCDateToScreen(); //print date to screen bool tsens; while(1) { tsens = bme.begin(); if(tsens == true) { switch(bme.chipModel()) { case BME280::ChipModel_BME280: Serial.println(F("Found BME280 sensor!")); break; case BME280::ChipModel_BMP280: Serial.println(F("Found BMP280 sensor! No Humidity available.")); break; default: Serial.println(F("Error, found unknown sensor.")); } break; } else { delay(2000); retries++; if(retries > 4) { Serial.println(F("Could not find BME280 sensor.")); //display error message on screen display.setFont(&FreeSansBold11pt7b); display.setTextColor(GxEPD_BLACK); int16_t tbx, tby; uint16_t tbw, tbh; display.getTextBounds("SENS ERR!", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t y = (92 - tbh) - tby + 20; display.setPartialWindow(0, 70 + 20, display.width(), 33); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(6, y); display.print(F("SENS ERR!")); } while (display.nextPage()); bmeerr = 1; break; } } } Serial.println(F("Setup complete")); }; /********************************* MAINLOOP ***************************************/ void loop() { static byte oldmins; RTC.readTime(); if(RTC.m != oldmins){ //run this every minute printRTCTimeToScreen(); //update time on screen if(bmeerr == 0){ //only attempt to measure anything if the sensor is present doStuff(); //acquire data sendDataToServer(); //send over to esp for publishing printDataToScreen(); //update values on screen } oldmins = RTC.m; //save old value } else if((RTC.m == 0 || RTC.m == 30) && RTC.s == 55) { delay(1100); //delay to prevent firing multiple times display.refresh(); //perform a full display refresh every half hour, removes quick refresh artifacts } else if(RTC.m == 0 && RTC.h == 0 && RTC.s == 40){ delay(1100); //delay to prevent firing multiple times printRTCDateToScreen(); //refresh date on midnight } else if(RTC.m == 0 && RTC.h == 2 && RTC.s == 33) { //resync time at 2am static byte retries = 0; bool t; while(1){ delay(2000); t = syncTime(); if(t == true) { break; } else { retries++; if(retries > 4){ //break after 5 tries Serial.println(F("Could not sync time\n")); break; } } } } delay(500); }; /************************************************************************************/ void doStuff() { dataset.temp = measureTemp(); dataset.hum = measureHum(); dataset.rhum = round(dataset.hum); dataset.pres = measurePres(); dataset.rpres = round(dataset.pres); dataset.co2 = measureCO2(); }; bool readNetInfo() { char buf; bool commRecvd = false; static byte i=0; static bool recv = false; while(Serial2.available() > 0) { buf = Serial2.read(); if(recv == true) { if(buf != '\n'){ incoming[i] = buf; i++; } else { incoming[i] = '\0'; recv = false; i = 0; commRecvd = true; } } else if (buf == '!'){ recv = true; } } i = 0; if(commRecvd == true){ char* token = strtok(incoming, ","); while(token != NULL){ if(i == 0){ sprintf(ssid,"%s", token); }; if(i == 1) { sprintf(ip,"%s", token); }; token = strtok(NULL, ","); i++; } i = 0; commRecvd = false; Serial.print("]"); Serial.println(); Serial.print(F("Got conn info:\n")); Serial.println(ssid); Serial.println(ip); printConnInfo(); return true; } return false; } bool syncTime() { char tbuf; static bool trecvd = false; static bool trecvn = false; char tarry[21]; static byte j = 0; byte dst; dst = checkDST(); Serial.print(F("Syncing time...\n")); delay(10); Serial2.print("?ntp"); Serial2.print("\n"); delay(300); while(Serial2.available() > 0 && trecvd == false){ tbuf = Serial2.read(); if(trecvn == true) { if(tbuf != '\n'){ tarry[j] = tbuf; j++; } else { tarry[j] = '\0'; trecvn = false; j = 0; trecvd = true; } } else if (tbuf == '>'){ trecvn = true; } } j = 0; if(trecvd == true){ Serial.println(tarry); char* token = strtok(tarry, "/"); while(token != NULL){ if(j == 0){ RTC.yyyy = atoi(token); }; if(j == 1) { RTC.mm = atoi(token); }; if(j == 2) { RTC.dd = atoi(token); }; if(j == 3) { if(dst == 1) { if(atoi(token) == 23) { RTC.h = 0; } else { RTC.h = atoi(token) + 1; } } if(dst == 0) { RTC.h = atoi(token); } }; if(j == 4){ RTC.m = atoi(token); }; if(j == 5){ RTC.s = atoi(token); }; token = strtok(NULL, "/"); j++; } j = 0; RTC.writeTime(); Serial.println(F("NTP sync complete")); trecvd = false; return true; } return false; } void layoutScreen() { display.setRotation(0); //portrait, connector at the top display.setFullWindow(); display.setFont(&TomThumb); display.setTextColor(GxEPD_BLACK); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(8, 69+20); display.print(F("Temperature:")); display.setCursor(8, (69 + 40 + 20)); display.print(F("Humidity:")); display.setCursor(8, (69 + 80) + 20); display.print(F("Pressure:")); display.setCursor(8, (69 + 120) + 20); display.print(F("CO2 concentration:")); display.setCursor(8, 269); display.print(F("Network info:")); } while (display.nextPage()); } void printConnInfo() { display.setFont(&TomThumb); display.setTextColor(GxEPD_BLACK); int16_t tbx, tby; uint16_t tbw, tbh; display.getTextBounds(ssid, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = 10; uint16_t y = 277; display.getTextBounds(ip, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x2 = 10; uint16_t y2 = 284; display.setPartialWindow(0, 270, display.width(), 20); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x,y); display.print("SSID: "); display.print(ssid); display.setCursor(x2,y2); display.print("IP: "); display.print(ip); } while (display.nextPage()); } void printRTCTimeToScreen() { char delim[] = ":"; RTC.readTime(); display.setFont(&FreeSans22pt7b); display.setTextColor(GxEPD_BLACK); int16_t tbx, tby; uint16_t tbw, tbh; display.getTextBounds(delim, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((display.width() / 2) - tbw); uint16_t y = ((32 - tbh) / 2) - (tby - 4) + 10; display.getTextBounds("00", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x2 = (display.width() / 2) - 8 - tbw ; uint16_t y2 = ((32 - tbh) / 2) - ( tby - 4) + 10; display.getTextBounds("00", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x3 = (display.width() / 2) + 8; display.setPartialWindow(0, 0, display.width(), (display.height() - (display.height() - 32)) + 20); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x, y); display.print(delim); display.setCursor(x2, y2); if(RTC.h < 10) { display.print("0"); display.print(RTC.h); } else { display.print(RTC.h); } display.setCursor(x3, y2); if(RTC.m < 10) { display.print("0"); display.print(RTC.m); } else { display.print(RTC.m); } } while(display.nextPage()); } void printRTCDateToScreen() { RTC.readTime(); display.setFont(&FreeSansBold9pt7b); display.setTextColor(GxEPD_BLACK); int16_t tbx, tby; uint16_t tbw, tbh; display.getTextBounds("00 - 00 - 0000", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = (display.width() / 2) - (tbw / 2) ; uint16_t y = 48 + (tbh / 2) + 20; display.setPartialWindow(0, 33 + 20, display.width(), 31); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x, y); if(RTC.dd < 10) { display.print("0"); display.print(RTC.dd); } else { display.print(RTC.dd); } display.print(" - "); if(RTC.mm < 10){ display.print("0"); display.print(RTC.mm); } else { display.print(RTC.mm); } display.print(" - "); display.print(RTC.yyyy); } while(display.nextPage()); } void printDataToScreen() { struct data *dtsp = &dataset; display.setFont(&FreeSansBold11pt7b); display.setTextColor(GxEPD_BLACK); int16_t tbx, tby; uint16_t tbw, tbh; display.getTextBounds("00.0 C", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((display.width() - tbw) / 2) - tbx; uint16_t y = (92 - tbh) - tby +20; display.getTextBounds("00 %", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x2 = ((display.width() - tbw) / 2) - tbx; uint16_t y2 = (132 - tbh) - tby + 20; display.getTextBounds("1488 hpa", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x3 = ((display.width() - tbw) / 2) - tbx; uint16_t y3 = ( 176 - tbh) - tby + 20; display.getTextBounds("1488 ppm", 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t y4 = ( 216 - tbh) - tby + 20; display.setPartialWindow(0, 70 + 20, display.width(), 33); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x, y); display.print(dtsp -> temp, 1); display.drawCircle(x+48,80 + 20,3,GxEPD_BLACK); display.drawCircle(x+48,80 + 20,2,GxEPD_BLACK); //quick and dirty way to get a "degrees symbol" display.print(F(" C")); } while (display.nextPage()); display.setPartialWindow(0, 110 + 20, display.width(), 33); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x2, y2); display.print(dtsp -> rhum); display.print(F(" %")); } while(display.nextPage()); display.setPartialWindow(0, 150 + 20,display.width(), 33); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x3, y3); display.print(dtsp -> rpres); display.print(F(" hPa")); } while(display.nextPage()); display.setPartialWindow(0, 190 +20 ,display.width(), 33); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(x3, y4); display.print(dtsp -> co2); display.print(F(" ppm")); } while(display.nextPage()); } float measureTemp() { BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); int temps[5]; //light led while measuring digitalWrite(13, HIGH); Serial.print(F("Measuring temperature")); for(char i = 0; i < 5; i++){ temps[i] = bme.temp() * 100; Serial.print(F(".")); delay(1000); } float temp; temp = (temps[0] + temps[1] + temps[2] + temps[3] + temps[4]) / 5; Serial.print(F("done: ")); Serial.println(temp / 100); digitalWrite(13, LOW); return temp/100; } float measureHum() { int hums[5]; //light led while measuring digitalWrite(13, HIGH); Serial.print(F("Measuring humidity")); for(char i = 0; i < 5; i++){ hums[i] = bme.hum() * 100; Serial.print(F(".")); delay(1000); } float hum; hum = (hums[0] + hums[1] + hums[2] + hums[3] + hums[4]) / 5; Serial.print(F("done: ")); Serial.println(hum / 100); digitalWrite(13, LOW); return hum / 100; } float measurePres() { BME280::PresUnit presUnit(BME280::PresUnit_hPa); float presrs[5]; //light led while measuring digitalWrite(13, HIGH); Serial.print(F("Measuring pressure")); for(char i = 0; i < 5; i++){ presrs[i] = bme.pres(BME280::PresUnit_hPa); Serial.print(F(".")); delay(1000); } float pres; pres = (presrs[0] + presrs[1] + presrs[2] + presrs[3] + presrs[4]) / 5; Serial.print(F("done: ")); Serial.println(pres); digitalWrite(13, LOW); return pres; } int16_t measureCO2(){ digitalWrite(13, HIGH); Serial.print(F("Measuring CO2.....")); int16_t co2conc = get_co2_concentration(uint16_t(dataset.pres * 10)); Serial.print(F("done:")); Serial.println(co2conc); digitalWrite(13, LOW); return(co2conc); } bool sendDataToServer(){ char payload[32]; char stemp[8]; char shum[8]; char check[] = "="; dtostrf(dataset.temp, 5, 2, stemp); dtostrf(dataset.hum, 5, 2, shum); //convert floats to strings sprintf(payload, "%s,%s,%s,%d,%d", check, stemp, shum, dataset.rpres, dataset.co2); //convert everything to a string Serial2.print(payload); //send over serial 2 to esp Serial2.print('\n'); //terminate transmission delay(1000); //give time to process comm Serial.print(F("Data sent\r\n")); return(true); } byte checkDST() { byte DST; RTC.readTime(); // ********************* Calculate offset for Sunday ********************* int y = RTC.yyyy - 2000; //take only last two digits of year (while not universal, this will work for the next 80 years so good enough for me) int x = (y + y/4 + 2) % 7; // remainder will identify which day of month // is Sunday by subtracting x from the one // or two week window. First two weeks for March // and first week for November // *********** BEGINS on 2nd Sunday of March @ 2:00 AM ********* if(RTC.mm == 3 && RTC.dd == (14 - x) && RTC.h >= 2) { DST = 1; } if((RTC.mm == 3 && RTC.dd > (14 - x)) || RTC.mm > 3) { DST = 1; } // ************* ENDS on 1st Sunday of Nov @ 2:00 AM ************ if(RTC.mm == 11 && RTC.dd == (7 - x) && RTC.h >= 2) { DST = 0; } if((RTC.mm == 11 && RTC.dd > (7 - x)) || RTC.mm > 11 || RTC.mm < 3) { DST = 0; } return(DST); }