#include <SPI.h>
#include <Wire.h>
#include "Adafruit_GFX.h" // include Adafruit graphics library
#include "Adafruit_ILI9341.h" // include Adafruit ILI9341 TFT library
//#include "Adafruit_BME280.h" // include Adafruit BME280 sensor library
#include "DHT.h"
#define TFT_RST 8 // TFT RST pin is connected to arduino pin 8
#define TFT_CS 9 // TFT CS pin is connected to arduino pin 9
#define TFT_DC 10 // TFT DC pin is connected to arduino pin 10
// initialize ILI9341 TFT library
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// Define which pins are used by the sensors
#define SENSOR_IN_PIN 2
#define SENSOR_OUT_PIN 3
DHT in_dht = DHT(SENSOR_IN_PIN, DHT22);
DHT out_dht = DHT(SENSOR_OUT_PIN, DHT22);
#define STR_WEATHER_STATION "Weather station"
#define STR_VERSION "Version 2019/0830"
#define STR_AUTHOR "Software by: ?"
#define STR_LICENCE "Licence: GNU GPL v3"
// ==================================================================
// According to Adafruit_GFX docs, font size is 5x8.
// Width macro is set to 6 instead, as strings seem to put a 1px spacing between characters.
#define FONT_W 6
#define FONT_H 8
#define COLOUR_IN ILI9341_RED
#define COLOUR_OUT ILI9341_DARKGREEN
// Total reads are 144 because because 144 / 24 gives 6 reads per hour, and 2px per read gives 288px, which fits nicely in 320px width.
// Min reads determine the max scale (6ph = 24 hours).
// Max reads determine the starting scale (192ph = 45min; 768ph = 12.5min)
#define READS_TOTAL 144
#define READS_PER_HOUR_MIN 6
#define READS_PER_HOUR_MAX 768
unsigned long int ReadsPerHour;
int in_mem[READS_TOTAL];
int in_index = READS_TOTAL-1;
int in_count = 0;
int in_current;
int out_mem[READS_TOTAL];
int out_index = READS_TOTAL-1;
int out_count = 0;
int out_current;
#define STORE_DELAY ((60UL*60UL*1000UL)/ReadsPerHour)
unsigned long int next_store_millis;
#define GRAPH_READ_W 2
#define GRAPH_X_MARGIN (320 - ((READS_TOTAL) * GRAPH_READ_W) - 1)
#define GRAPH_Y_AREA 210
int graph_temp_max = +300;
int graph_temp_min = -200;
int graph_temp_h = 4;
void printTemperature(int temp, const int x, const int colour) {
tft.fillRect(x, 0, FONT_W*3*7, FONT_H*3, ILI9341_BLACK);
String text = (temp > 0) ? "+" : (temp < 0) ? "-" : "";
temp = abs(temp);
text += (temp / 10);
text += ".";
text += (temp % 10);
text += "C";
tft.setCursor(x, 0);
tft.setTextColor(colour);
tft.print(text);
}
void printCurrentTemperatures(void) {
tft.setTextSize(3);
printTemperature(in_current, GRAPH_X_MARGIN, COLOUR_IN);
printTemperature(out_current, 176, COLOUR_OUT);
}
int calculateNewScale(void) {
const int old_temp_max = graph_temp_max;
const int old_temp_min = graph_temp_min;
graph_temp_max = graph_temp_min = 0;
for(int i = 0; i < in_count; ++i) {
if(in_mem[i] > graph_temp_max)
graph_temp_max = in_mem[i];
else if(in_mem[i] < graph_temp_min)
graph_temp_min = in_mem[i];
}
for(int o = 0; o < out_count; ++o) {
if(out_mem[o] > graph_temp_max)
graph_temp_max = out_mem[o];
else if(out_mem[o] < graph_temp_min)
graph_temp_min = out_mem[o];
}
// Make sure we include 0 on the scale
if(graph_temp_max < 0) graph_temp_max = 0;
if(graph_temp_min > 0) graph_temp_min = 0;
// Round up or down to nearest 10 degrees (100 steps)
if(graph_temp_max % 100) graph_temp_max = graph_temp_max - (graph_temp_max % 100) + 100;
if(graph_temp_min % 100) graph_temp_min = graph_temp_min - (graph_temp_min % 100) - 100;
// Just to be sure, when both min and max are 0 - force scale to [-10, +10]
if((graph_temp_min == 0) && (graph_temp_max == 0)) {
graph_temp_max = +100;
graph_temp_min = -100;
}
graph_temp_h = GRAPH_Y_AREA / ((graph_temp_max/10) - (graph_temp_min/10));
return ((graph_temp_max != old_temp_max) || (graph_temp_min != old_temp_min));
}
int temperatureToYPos(const int temp) {
return 239 - (graph_temp_h * (temp - graph_temp_min))/10;
}
int graphMaxY(void) {
return temperatureToYPos(graph_temp_max);
}
void drawScale(void) {
tft.drawLine(GRAPH_X_MARGIN-1, graphMaxY(), GRAPH_X_MARGIN-1, 239, ILI9341_LIGHTGREY);
tft.setTextColor(ILI9341_LIGHTGREY);
tft.setTextSize(1);
for(int temp = graph_temp_min; temp <= graph_temp_max; temp += 100) {
String text = (temp > 0) ? "+" : "";
text += temp / 10;
int ypos = temperatureToYPos(temp);
tft.setCursor(30 - 6*text.length(), ypos-4);
tft.print(text);
}
}
void drawTimeLabels(void) {
tft.setTextColor(ILI9341_LIGHTGREY);
tft.setTextSize(1);
int ypos = graphMaxY() - FONT_H - 1;
for(int i = 3; i > 0; --i) {
int number;
char unit;
if((READS_TOTAL / ReadsPerHour) >= 4) {
number = (4-i) * (int)((10L * READS_TOTAL) / ReadsPerHour / 4);
unit = 'h';
} else {
number = ((4-i) * (int)((10L * 60L * READS_TOTAL) / ReadsPerHour))/4;
unit = 'm';
}
String text = "-";
text += (number / 10);
if(number % 10) { text += '.'; text += (number % 10); }
text += unit;
int xpos = GRAPH_X_MARGIN + 1 + GRAPH_READ_W*((i*READS_TOTAL)/4 - 1);
xpos -= (text.length() * FONT_W)/2;
tft.setCursor(xpos, ypos);
tft.print(text);
}
}
void drawAxes(void) {
tft.setTextColor(ILI9341_LIGHTGREY);
tft.setTextSize(1);
// Horizontal lines so temperature levels are easily visible
for(int temp = graph_temp_min; temp <= graph_temp_max; temp += 100) {
int ypos = temperatureToYPos(temp);
tft.drawLine(GRAPH_X_MARGIN, ypos, 319, ypos, (temp == 0) ? ILI9341_LIGHTGREY : ILI9341_DARKGREY);
}
// Vertical lines to easily check for temperature 6, 12 and 18 hours ago
int ymax = graphMaxY();
for(int i = 3; i > 0; --i) {
int xpos = GRAPH_X_MARGIN + 1 + GRAPH_READ_W*((i*READS_TOTAL)/4 - 1);
tft.drawLine(xpos, ymax, xpos, 239, ILI9341_DARKGREY);
}
}
void drawLine(const int *const data, const int endsAt, const int count, const int colour) {
if(count == 0) return;
int start = endsAt - count + 1;
if(start < 0) start += READS_TOTAL;
int old_x = 320 - (count * GRAPH_READ_W);
int old_y = temperatureToYPos(data[start]);
if(count == 1) {
tft.drawPixel(319, old_y, colour);
return;
}
for(int i = 1; i < count; ++i) {
int index = (start + i) % READS_TOTAL;
int new_x = old_x + GRAPH_READ_W;
int new_y = temperatureToYPos(data[index]);
tft.drawLine(old_x, old_y, new_x, new_y, colour);
old_x = new_x;
old_y = new_y;
}
}
void drawGraph(const int fullRedraw) {
if(fullRedraw) {
tft.fillRect(0, 0, 320, 240, ILI9341_BLACK);
drawScale();
} else {
int xpos = (in_count > out_count) ? in_count : out_count;
xpos = 320 - (xpos * GRAPH_READ_W);
tft.fillRect(xpos, FONT_H*3, 320, 240, ILI9341_BLACK);
}
drawAxes();
drawTimeLabels();
drawLine(in_mem, in_index, in_count, COLOUR_IN);
drawLine(out_mem, out_index, out_count, COLOUR_OUT);
}
void readDHT(DHT *dht, int *const current_temp) {
dht->read();
*current_temp = dht->readTemperature() * 10.0;
}
void readTemperatures() {
readDHT(&in_dht, &in_current);
readDHT(&out_dht, &out_current);
}
int average(const int a, const int b) {
if( ((a <= 0) && (b >= 0)) || ((a >= 0) && (b <= 0)) ) return (a+b)/2;
int a_half = a / 2;
int a_mod = a % 2;
int b_half = b / 2;
int b_mod = b % 2;
return a_half + b_half + (a_mod & b_mod);
}
void storemem(const int current_temp, int *const mem, int *const index, int *const count) {
if((*count == READS_TOTAL) && (ReadsPerHour > READS_PER_HOUR_MIN)) {
for(int i = 0; i < READS_TOTAL/2; ++i) {
mem[i] = average(mem[i*2], mem[i*2 +1]);
}
*count = READS_TOTAL/2;
*index = *count-1;
}
*index = ((*index)+1) % READS_TOTAL;
mem[*index] = current_temp;
if(*count < READS_TOTAL) *count += 1;
}
int storeTemperatues() {
int old_in_count = in_count;
int old_out_count = out_count;
storemem(in_current, in_mem, &in_index, &in_count);
storemem(out_current, out_mem, &out_index, &out_count);
next_store_millis += STORE_DELAY;
if((old_in_count > in_count) || (old_out_count > out_count)) {
ReadsPerHour /= 2;
return 1;
}
return 0;
}
void titlescreen() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(3);
tft.setTextColor(ILI9341_ORANGE);
String text = STR_WEATHER_STATION;
tft.setCursor((320 - 3*FONT_W*text.length())/2, 96);
tft.print(text);
tft.setTextSize(2);
tft.setTextColor(ILI9341_YELLOW);
text = STR_VERSION;
tft.setCursor((320 - 2*FONT_W*text.length())/2, 124);
tft.print(text);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE);
text = STR_AUTHOR;
tft.setCursor((320 - FONT_W*text.length())/2, 220);
tft.print(text);
text = STR_LICENCE;
tft.setCursor((320 - FONT_W*text.length())/2, 230);
tft.print(text);
}
void setup() {
tft.begin();
tft.setRotation(1);
titlescreen();
in_dht.begin();
out_dht.begin();
ReadsPerHour = READS_PER_HOUR_MAX;
randomSeed(analogRead(0));
delay(3500);
tft.fillScreen(ILI9341_BLACK);
drawScale();
drawAxes();
next_store_millis = millis();
}
#define LOOP_MIN_DELAY 4000
void loop(void) {
unsigned long int current_millis = millis();
readTemperatures();
if((in_count == 0) || (out_count == 0) || (current_millis >= next_store_millis)) {
int needsRedraw = 0;
needsRedraw |= storeTemperatues();
needsRedraw |= calculateNewScale();
drawGraph(needsRedraw);
}
printCurrentTemperatures();
// Depending on Y scale, the time labels may get overdrawn by the current temps.
if(graphMaxY() - FONT_H - 1 < FONT_H*3) drawTimeLabels();
if(next_store_millis - current_millis <= LOOP_MIN_DELAY*2)
delay(next_store_millis - current_millis);
else
delay(LOOP_MIN_DELAY);
}
// ==================================================================
#if 0
// define device I2C address: 0x76 or 0x77
(0x77 is library default address)
#define BME280_I2C_ADDRESS 0x76
Adafruit_BME280 bme280; // initialize Adafruit BME280 library
void setup(void)
{
tft.initR(INITR_144GREENTAB); // initialize a ST7735S chip, black tab
tft.fillScreen(ILI_BLACK); // fill screen with black color
tft.drawFastHLine(0, 15 , tft.width(), ST77XX_CYAN); // draw horizontal white line at position (0, 30)
tft.setTextColor(ST77XX_CYAN, ST77XX_BLACK); // set text color to white and black background
tft.setTextSize(1); // text size = 1
//tft.setCursor(4, 0);
// move cursor to position (4, 0) pixel
//tft.print("ARDUINO + ST7735 TFT");
tft.setCursor(19, 5); // move cursor to position (19, 15) pixel
tft.print("WEATHER STATION");
// initialize the BME280 sensor
if( bme280.begin(BME280_I2C_ADDRESS) == 0 )
{ // connection error or device address wrong!
tft.setTextColor(ST77XX_RED, ST77XX_CYAN); // set text color to red and black background
tft.setTextSize(2); // text size = 2
tft.setCursor(5, 76); // move cursor to position (5, 76) pixel
tft.print("Connection");
tft.setCursor(35, 100);
// move cursor to position (35, 100) pixel
tft.print("Error");
while(1); // stay here
}
tft.drawFastHLine(0, 55, tft.width(), ST77XX_CYAN); // draw horizontal white line at position (0, 76)
tft.drawFastHLine(0, 95, tft.width(), ST77XX_CYAN); // draw horizontal white line at position (0, 122)
tft.setTextColor(ST77XX_RED, ST77XX_BLACK); // set text color to red and black background
tft.setCursor(25, 20); // move cursor to position (25, 39) pixel
tft.print("TEMPERATURE ");
tft.setTextColor(ST77XX_CYAN, ST77XX_BLACK); // set text color to cyan and black background
tft.setCursor(34, 60); // move cursor to position (34, 85) pixel
tft.print("HUMIDITY ");
tft.setTextColor(ST77XX_GREEN, ST7735_BLACK); // set text color to green and black background
tft.setCursor(34, 100); // move cursor to position (34, 131) pixel
tft.print("PRESSURE ");
tft.setTextSize(2); // text size = 2
}
// main loop
void loop()
{
char _buffer[8];
// read temperature, humidity and pressure from the BME280 sensor
float temp = bme280.readTemperature(); // get temperature in °C
float humi = bme280.readHumidity(); // get humidity in rH%
float pres = bme280.readPressure(); // get pressure in Pa
// print temperature (in °C)
if(temp < 0) // if temperature < 0
sprintf( _buffer, "-%02u.%02u", (int)abs(temp), (int)(abs(temp) * 100) % 100 );
else // temperature >= 0
sprintf( _buffer, " %02u.%02u", (int)temp, (int)(temp * 100) % 100 );
tft.setTextColor(ST77XX_YELLOW, ST77XX_BLACK); // set text color to yellow and black background
tft.setCursor(11, 34);
tft.print(_buffer);
tft.drawCircle(89, 34, 2, ST77XX_YELLOW); // print degree symbol ( ° )
tft.setCursor(95, 34);
tft.print("C");
// 2: print humidity
sprintf( _buffer, "%02u.%02u %%", (int)humi, (int)(humi * 100) % 100 );
tft.setTextColor(ST77XX_MAGENTA, ST77XX_BLACK); // set text color to magenta and black background
tft.setCursor(23, 74);
tft.print(_buffer);
// 3: print pressure (in hPa)
sprintf( _buffer, "%04u.%02u", (int)(pres/100), (int)((uint32_t)pres % 100) );
tft.setTextColor(ST77XX_ORANGE, ST77XX_BLACK); // set text color to orange and black background
tft.setCursor(3, 112);
tft.print(_buffer);
tft.setCursor(91, 112);
tft.print("hPa");
delay(1000); // wait a second
}
#endif