#include "Ucglib.h"
#include <Adafruit_BME280.h>
#include "RTClib.h"
#include <SoftwareSerial.h>
#define SPI_SCK 13
#define SPI_MOSI 11
#define SPI_CS 10
#define SPI_CD 9
#define SPI_RES 8
/* Latimea fontului folosit pentru afisarile pe ecran
* (difera in functie de font si e fixa doar pentru
* fonturile monospace)
*/
#define FONT_WIDTH 12
/* Adresa senzorului BME280 (trebuie mentionata explicit
* in constructorul begin, altfel senzorul nu va functiona
*/
#define BME_ADDRESS 0X76
/*
* Culorile pentru fundalul, respectiv scrisul ecranului
*/
#define BACKGROUND_RED 0
#define BACKGROUND_GREEN 0
#define BACKGROUND_BLUE 0
#define WRITE_RED 255
#define WRITE_GREEN 255
#define WRITE_BLUE 255
/*
* Valoare folosita pentru a plasa afisarile pe ecran
* (nu vrem sa afisam direct in colturi din motive de estetica)
*/
#define OFFSET 20
#define AIR_PIN 0
/*
* Valori pentru plasarea datelor preluate de la senzori
* pe ecran
*/
#define TEMP_X 20
#define BAR_X 40
#define HUM_X 60
#define AIR_X 80
#define TX_PIN 3
#define RX_PIN 2
/*
* Durata de timp intre doua transmii prin Bluetooth
*/
#define BLUE_TIME 5000
/*
* Pinii la care sunt conectati butoanele
*/
#define MODE_BUTTON 4
#define INC_BUTTON 6
#define DEC_BUTTON 5
/*
* Pentru cate ore retinem date preluate de la senzori
*/
#define HIST_COUNT 40
/*
* Starile prin care poate cicla afisarea pe ecran
*/
#define NORMAL_STATE 0
#define MAX_STATE 1
#define MIN_STATE 2
#define AVG_STATE 3
Ucglib_ILI9341_18x240x320_HWSPI ucg(SPI_CD, SPI_CS, SPI_RES);
Adafruit_BME280 bme;
String oldTemp = "", oldHum = "", oldBar = "", oldAir = "";
RTC_DS3231 rtc;
SoftwareSerial Blue(TX_PIN, RX_PIN);
unsigned long transmissionTime; /* Variabila folosita pentru a determina cand sa trimitem date prin Bluetooth */
float temp[HIST_COUNT], bar[HIST_COUNT], hum[HIST_COUNT];
int air[HIST_COUNT];
/*
* Determina daca suntem la momentul in care sa scriem in istoric
*/
bool writeToHistory = false;
/*
* Ne spune cate scrieri avem in istoric
*/
short int writeCount = 0;
short int state = NORMAL_STATE, oldState = NORMAL_STATE;
/*
* Stari pentru butoane plus pentru cate ore se va face afisarea pe ecran in cazul in care nu ne aflam in
* starea uzuala
*/
bool oldModeState = HIGH, oldIncState = HIGH, oldDecState = HIGH;
short int hourCount = 1;
void setup() {
Serial.begin(9600);
Blue.begin(9600);
pinMode(MODE_BUTTON, INPUT_PULLUP);
pinMode(INC_BUTTON, INPUT_PULLUP);
pinMode(DEC_BUTTON, INPUT_PULLUP);
rtc.begin();
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
bme.begin(BME_ADDRESS);
ucg.begin(UCG_FONT_MODE_SOLID);
ucg.clearScreen();
/*
* Alegerea fontului s-a facut in primul rand monospace (m-ul din nume) pentru ca orice caracter sa aiba
* aceeasi latime pe ecran. Acest lucru ne permite ca atunci cand modificam un sir pe ecran sa putem sterge
* si rescrie doar caracterele care s-au schimbat, deoarece stim sigur pozitia la care se afla
*
* Afisarea e rotita pentru a se afisa pe orizontala
*/
ucg.setFont(ucg_font_courB14_mf);
ucg.setRotate90();
transmissionTime = millis();
}
/*
* Functie de actualizare a afisarii pe ecran in cazul starii uzuale. Stiind care este
* latimea fontului, ne putem pozitiona acolo unde caracterele s-au modificat
* Avem 3 cazuri:
* - fie avem de schimbat o litera din sirul vechi cu una din sirul nou
* - fie scriem o parte din sirul nou acolo unde inainte nu aveam de scris nimic
* - fie trebuie sa stergem partea din sirul vechi aflata dincolo de sirul nou
*
* De asemenea, aici verificam si daca e momentul sa trimitem date prin Bluetooth
*/
void updateString(String& oldString, String& newString, unsigned char y, bool printBlue) {
char i, oldLength = oldString.length(), newLength = newString.length();
if (state == NORMAL_STATE) {
for (i = 0; i < oldLength && i < newLength; ++i) {
if (oldString[i] != newString[i]) {
ucg.setPrintPos(OFFSET + FONT_WIDTH * i, y);
ucg.setColor(BACKGROUND_RED, BACKGROUND_GREEN, BACKGROUND_BLUE);
ucg.print(oldString[i]);
ucg.setPrintPos(OFFSET + FONT_WIDTH * i, y);
ucg.setColor(WRITE_RED, WRITE_GREEN, WRITE_BLUE);
ucg.print(newString[i]);
}
}
ucg.setPrintPos(OFFSET + FONT_WIDTH * oldLength, y);
ucg.setColor(WRITE_RED, WRITE_GREEN, WRITE_BLUE);
if (newLength > oldLength) {
ucg.print(newString.substring(oldLength));
}
ucg.setPrintPos(OFFSET + FONT_WIDTH * newLength, y);
ucg.setColor(BACKGROUND_RED, BACKGROUND_GREEN, BACKGROUND_BLUE);
if (oldLength > newLength) {
ucg.print(oldString.substring(newLength));
}
}
if (printBlue) {
Blue.println(newString);
}
oldString = state == NORMAL_STATE ? newString : "";
}
/*
* Functie pentru calcularea dupa caz a valorilor maxime, minime sau medii din istoric
*/
void getRezValues(float &rezTemp, float &rezBar, float &rezHum, int &rezAir) {
char i;
ucg.setPrintPos(OFFSET, OFFSET);
if (state == MAX_STATE) {
ucg.print((String)"Max Values " + (String)hourCount + (String)" hours");
rezTemp = -85.0;
rezBar = 0.0;
rezHum = 0.0;
rezAir = 0;
for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
rezTemp = temp[i] > rezTemp ? temp[i] : rezTemp;
rezBar = bar[i] > rezBar ? bar[i] : rezBar;
rezHum = hum[i] > rezHum ? hum[i] : rezHum;
rezAir = air[i] > rezAir ? air[i] : rezAir;
}
}
if (state == MIN_STATE) {
ucg.print((String)"Min Values " + (String)hourCount + (String)" hours");
rezTemp = 45.0;
rezBar = 200000.0;
rezHum = 200.0;
rezAir = 1000;
for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
rezTemp = temp[i] < rezTemp ? temp[i] : rezTemp;
rezBar = bar[i] < rezBar ? bar[i] : rezBar;
rezHum = hum[i] < rezHum ? hum[i] : rezHum;
rezAir = air[i] < rezAir ? air[i] : rezAir;
}
}
if (state == AVG_STATE) {
ucg.print((String)"Avg Values " + (String)hourCount + (String)" hours");
rezTemp = 0.0;
rezBar = 0.0;
rezHum = 0.0;
rezAir = 0;
for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
rezTemp += temp[i];
rezBar += bar[i];
rezHum += hum[i];
rezAir += air[i];
}
rezTemp /= (float)hourCount;
rezBar /= (float)hourCount;
rezHum /= (float)hourCount;
rezAir /= hourCount;
}
}
/*
* Aici actualizam scrisul de pe ecran daca ne aflam in starea uzuala sau preluam valorile dorite din istoric
* daca este necesar (nu ne aflam in starea uzuala), De asemenea, tot aici scriem si noi valori in istoric
* daca e cazul
*/
void updateData(bool printBlue, bool writeToHistory) {
float bmeTemp = bme.readTemperature(), bmeBar = bme.readPressure(), bmeHum = bme.readHumidity();
int airData = analogRead(AIR_PIN);
float rezTemp, rezBar, rezHum;
int rezAir;
updateString(oldTemp, (String)"Temp = " + bmeTemp + (String)" *C", TEMP_X, printBlue);
updateString(oldBar, (String)"Pressure = " + bmeBar / 100.0F + (String)" hPa", BAR_X, printBlue);
updateString(oldHum, (String)"Humidity = " + bmeHum + (String)" %", HUM_X, printBlue);
updateString(oldAir, (String)"Air PPM = " + airData + (String)" PPM", AIR_X, printBlue);
if (state != NORMAL_STATE) {
getRezValues(rezTemp, rezBar, rezHum, rezAir);
ucg.setPrintPos(OFFSET, TEMP_X + OFFSET);
ucg.print((String)"Temp = " + (String)rezTemp + (String)" *C");
ucg.setPrintPos(OFFSET, BAR_X + OFFSET);
ucg.print((String)"Pressure = " + rezBar / 100.0F + (String)" hPa");
ucg.setPrintPos(OFFSET, HUM_X + OFFSET);
ucg.print((String)"Humidity = " + rezHum + (String)" %");
ucg.setPrintPos(OFFSET, AIR_X + OFFSET);
ucg.print((String)"Air PPM = " + rezAir + (String)" PPM");
}
if (writeToHistory) {
temp[writeCount - 1] = bmeTemp;
bar[writeCount - 1] = bmeBar;
hum[writeCount - 1] = bmeHum;
air[writeCount - 1] = airData;
}
}
void loop() {
bool printBlue = abs(millis() - transmissionTime) >= BLUE_TIME;
DateTime now = rtc.now();
char i, rtcMin = now.minute();
bool currentModeState = digitalRead(MODE_BUTTON), currentIncState = digitalRead(INC_BUTTON), currentDecState = digitalRead(DEC_BUTTON);
/*
* Daca nu am scris nimic in istoric, nu are rost sa verificam daca butoanele sunt apasate
*/
if (writeCount) {
if (currentModeState == LOW && oldModeState == HIGH) {
if (state == NORMAL_STATE) {
state = MAX_STATE;
} else {
if (state == MAX_STATE) {
state = MIN_STATE;
} else {
if (state == MIN_STATE) {
state = AVG_STATE;
} else {
state = NORMAL_STATE;
}
}
}
ucg.clearScreen();
} else {
if (currentIncState == LOW && oldIncState == HIGH && state != NORMAL_STATE) {
hourCount += (hourCount < writeCount);
ucg.clearScreen();
} else {
if (currentDecState == LOW && oldDecState == HIGH && state != NORMAL_STATE) {
hourCount -= (hourCount > 1);
ucg.clearScreen();
}
}
}
oldModeState = currentModeState;
oldIncState = currentIncState;
oldDecState = currentDecState;
oldState = state;
}
/*
* Conditia echivalenta pentru salvarea a noi date in istoric
*/
if (!rtcMin && !writeToHistory) {
if (writeCount >= HIST_COUNT) {
for (i = 0; i < HIST_COUNT - 1; ++i) {
temp[i] = temp[i + 1];
bar[i] = bar[i + 1];
hum[i] = hum[i + 1];
air[i] = air[i + 1];
}
}
writeCount += (writeCount < HIST_COUNT);
if (state != NORMAL_STATE) {
ucg.clearScreen();
}
}
updateData(printBlue, !rtcMin && !writeToHistory);
writeToHistory = !rtcMin;
transmissionTime = printBlue ? millis() : transmissionTime;
if (printBlue) {
Blue.println();
}
}