#include <TFT_eSPI.h>
#include <SPI.h> // this is needed for display
#include <Wire.h> // this is needed for FT6206
#include <Adafruit_FT6206.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
#include "Free_Fonts.h"
// The FT6206 uses hardware I2C (SCL/SDA)
Adafruit_FT6206 ctp = Adafruit_FT6206();
// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 15
#define TFT_DC 2
#define TFT_MOSI 23
#define TFT_SCLK 18
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define GREY 0x2108
#define SCALE0 0xC655 // accent color for unused scale segments
#define SCALE1 0x5DEE // accent color for unused scale segments
#define TEXT_COLOR 0xFFFF // is currently white
#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5
#define DEG2RAD 0.0174532925 // conversion factor degrees to radials
// data connection and acquisition variables
const char* ssid = "Wokwi-GUEST"; // network wifi credentials
const char* password = ""; // wifi network key
String openWeatherMapApiKey = "ab7f52a62676a8f0cc0fa5a5bc4dc32b"; // API Openweathermap account
String city = "Moscow";
String countryCode = "ru"; // "NL" for The Netherlands
String jsonDocument (1024); // memory allocation json string
// timer internet access for server download
unsigned long lastTime = 0; // testing purposes = 30 seconds
unsigned long timerDelay; // is set at 1 seconds in Setup and to 5 minutes in Loop
// joint variables
float temp_01=10;
float hum_01;
int hum_02;
// rainbow scale ring meter variables
uint32_t runTime = -99999; // time for next update
int reading = 10; // value to be displayed in circular scale
int tesmod = 0;
int rGaugePos_x = 0; // these two variables govern the position
int rGaugePos_y = 155; // of the square + gauge on the display
int ringmeterRadius = 65; // governs diameter of rainbow gauge
char* ringlabel[] = {(char *)"",(char *)"*C",(char *)"%",(char *)"mBar"}; // some custom labels
float tempRainbowgauge;
int t = 40; // governs position of numerical output rainbow scale
// small needle meter
int j;
int pivotNeedle_x = 165; // pivot coordinates needle of small gauge
int pivotNeedle_y = 222;
float center_x1 = 160; // center x of edge markers circle left gauge
float center_y1 = 228; // center y of edge markers circle left gauge
int radius_s = 65; // for scale markers
int needleLength = 45; // gauge needle length
int edgemarkerLength = 5; // edge marker length
float edge_x1, edge_y1, edge_x1_out, edge_y1_out;
float angleNeedle = 0;
float needle_x, needle_y;
float needle_x_old, needle_y_old;
float angleCircle = 0;
int pivot_x = 165; // pivot coordinates needle of small gauge
int pivot_y = 222;
// circle segment 'pie chart' meter variables
byte inc = 0;
unsigned int col = 0;
float pivotHumcircle__x = 198;
float pivotHumcircle__y = 274;
float startAngle = 0;
float subAngle; // subtended angle
int r = 34; // circle radius
// compass wind direction pointer
float compassPivot_x = 158;
float compassPivot_y = 65;
float c_x1, c_x2,c_x3, c_x4;
float c_y1, c_y2,c_y3, c_y4;
float c_x1_old,c_x2_old,c_x3_old, c_x4_old;
float c_y1_old,c_y2_old,c_y3_old, c_y4_old;
float windDir_01;
float compassAngle;
int compass_r = 22;
char* sector [] = {(char *)"N", (char *)"NE", (char *)"E", (char *)"SE", (char *)"S", (char *)"SW", (char *)"W", (char *)"NW", (char *)"N"}; // wind sector labels
int h;
void setup(void) {
Serial.begin(115200);
tft.begin();
if (!ctp.begin(40)) { // pass in 'sensitivity' coefficient
Serial.println("Couldn't start FT6206 touchscreen controller");
while (1);
}
tft.fillScreen (TFT_BLACK);
tft.setTextSize (2);
tft.println ("network ");
tft.println ("connecting");
WiFi.begin (ssid, password);
Serial.println ("connecting");
while(WiFi.status() != WL_CONNECTED) {
delay (500);
Serial.print (".");
tft.print (".");
}
Serial.println ();
Serial.print ("Connected to WiFi network ");
tft.println ("connected to ");
tft.println ();
Serial.print (ssid);
tft.println (ssid);
delay(1000);
tft.fillScreen (TFT_BLACK);
Serial.print (" - IP adress: ");
Serial.println (WiFi.localIP());
Serial.println ("timer set to 30 seconds (timerDelay variable) - it will take 30 seconds before publishing the first reading.");
drawAllWindowFrames (); // instructions moved to subroutine
tft.setTextColor (TFT_YELLOW,BLACK);
tft.setFreeFont(FF1);
tft.setTextSize (1);
tft.setCursor (21,20);
tft.print ("OpenWeather MSK");
tft.setCursor (9,45);
tft.print ("temp");
tft.setCursor (10,67);
tft.print ("baro");
tft.setCursor (10,89);
tft.print ("hum");
tft.setCursor (10,111);
tft.print ("wind");
tft.setCursor (10,133);
tft.print ("from:");
tft.setFreeFont(FF0); // note - units in Free Font 0 = text size 1
tft.setCursor (133,37);
tft.print (char(247)); // degree character
tft.print ("C");
tft.setCursor (133,59);
tft.print ("mm");
tft.setCursor (133,81);
tft.print ("%");
tft.setCursor (133,102);
tft.print ("m/s");
tft.setCursor (10,160);
tft.print ("temp"); // in rainbow scale
tft.setCursor (125,160); // left upper corner = anchoring point of rainbow gauge scale
tft.print (char(247)); // degree character
tft.setTextSize (2);
tft.print ("C");
tft.setTextSize (1);
tft.setCursor (55,290); // 0 is bottom rainbow scale temperature
tft.print ("0");
tft.setCursor (98,290); // 50 is top rainbow scale temperature
tft.print ("50");
tft.setCursor (220,160); // display percent sign in small gauge scale
tft.print ("%");
drawSaleSmallGauge ();
tft.fillCircle (pivot_x, pivot_y, 2, MAGENTA); // pivot needle middle small gauge // arbitrary seeding temp - avoids drawing black line from position 0.0
needleMeter (); // calculate position and draw the needle
tft.drawRoundRect (158,150,80,80,4, GREEN); // correction right middle small frame from buildup needle
tft.setCursor (216,217);
tft.setTextSize (1);
tft.print ("40"); // scale starts with 40% and ends with 100%
tft.setCursor (164,169);
tft.print ("100");
tft.setTextSize (2);
// wind sector window on top of compass
tft.setCursor (171, 51);
tft.setTextSize (1);
tft.print ("wind:");
tft.setCursor (170,33);
tft.setTextSize (1);
tft.setTextColor (TFT_GREEN);
tft.print ("v1.0 - FGW");
tft.setTextColor (TFT_WHITE);
timerDelay = 1000; // this is for fast initial query OpenWeather server for json string
doTheHardWork (); // run this here - in void loop the timerDelay refreshes every 5 minutes
drawAllWindowFrames (); // drawover to correct for all drawing errors caused by needles and markers
rainbowScaleMeter (); // prepare the rainbow scale meter
needleMeter (); // prepare the middle right, small meter with the needle
circleSegmentMeter (); // prepare the circle segment pie meter
compassGauge (); // prepare the wind compass meter
compassPointer (); // add pointer to the wind compass meter
windSectorReporter ();
}
void loop() {
timerDelay = 60000; // 600000 connect to OpenWeather and refresh every ten minutes
doTheHardWork ();
// --------------------------Testing proc-------------------------------
}
void doTheHardWork (){
// Send an HTTP GET request
if ((millis () - lastTime) > timerDelay)
{
// Check WiFi connection status
if(WiFi.status ()== WL_CONNECTED)
{
String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&APPID=" + openWeatherMapApiKey;
jsonDocument = httpGETRequest(serverPath.c_str ());
JSONVar myObject = JSON.parse (jsonDocument); // JSON.typeof(jsonVar) can be used to get the type of the var
double temp_01 = (myObject ["main"]["temp"]);
double hum_01 = (myObject ["main"]["humidity"]);
double press_01 = (myObject ["main"] ["pressure"]);
if (JSON.typeof (myObject) == "undefined")
{
Serial.println ("Parsing input failed!");
return;
}
Serial.println ("*******************************************");
Serial.print ("JSON object = ");
Serial.println (myObject);
Serial.println ("*******************************************");
Serial.println ("extracted from JSON object:");
Serial.print ("Temperature: ");
Serial.print (myObject["main"]["temp"]);
Serial.println (" *Kelvin");
Serial.print ("Pressure: ");
Serial.print (myObject["main"]["pressure"]);
Serial.println (" mB");
Serial.print ("Humidity: ");
Serial.print(myObject["main"]["humidity"]);
Serial.println (" %");
Serial.print ("Wind Speed: ");
Serial.print (myObject["wind"]["speed"]);
Serial.println (" m/s");
Serial.print("Wind Direction: ");
Serial.print (myObject["wind"]["deg"]);
Serial.println (" degrees");
double windDir = (myObject ["wind"]["deg"]);
windDir_01 = windDir;
compassGauge ();
compassPointer ();
windSectorReporter ();
// print some json data in upper window of the display
temp_01 = temp_01-273; // convert temp from Kelvin to Celsius
tempRainbowgauge = temp_01;
hum_02 = hum_01;
press_01 = press_01 * 0.75; // conver mB to mmH ----- 0,750063755419211;
Serial.print ("Wind from: ");
Serial.println (sector[h]);
Serial.print ("Rainbowgauge: ");
Serial.print (tempRainbowgauge,1);
Serial.println (" *C");
Serial.println ("");
tft.setFreeFont (FF1); // start display data representation
tft.fillRect (60,35,70,15,BLACK);
tft.setCursor (72,45);
tft.print (temp_01,1);
tft.fillRect (60,55,70,15,BLACK);
tft.setCursor (67,67);
//tft.print (myObject["main"]["pressure"]);
tft.print (press_01,1);
tft.fillRect (60,77,70,15,BLACK);
tft.setCursor (75,89);
tft.print (myObject["main"]["humidity"]);
tft.fillRect (60,99,70,15,BLACK);
tft.setCursor (69,110);
tft.print (myObject["wind"]["speed"]);
tft.fillRect (65,121,40,15,BLACK);
tft.setCursor (78,133);
tft.print (sector[h]); // wind sector
tft.setFreeFont(FF0); // end display numerical data representation
rainbowScaleMeter ();
needleMeter (); // calculate needle position middle right, small meter
circleSegmentMeter (); // calculate and display the pie chart
}
else {
Serial.println( "WiFi Disconnected");
}
lastTime = millis ();
}
}
void drawAllWindowFrames (){
tft.drawRoundRect ( 4, 2, 234, 25, 2, GREEN); // title frame
tft.drawRoundRect ( 4, 28, 150,118, 2, GREEN); // left upper numerical window
tft.drawRoundRect ( 4, 150, 150,165, 2, GREEN); // left lower (big) frame for rainbow scale gauge
tft.drawRoundRect (158, 150, 80, 80, 2, GREEN); // right middle small frame - needle meter
tft.drawRoundRect (158, 235, 80, 80, 2, GREEN);
tft.drawRoundRect (158, 65, 80, 81, 2, GREEN); // compass frame
tft.drawRoundRect (158, 47, 80, 16, 2, GREEN); // wind sector indicator frame
tft.drawRoundRect (158, 28, 80, 16, 2, GREEN); // version window
}
String httpGETRequest(const char* serverName) {
HTTPClient http;
http.begin(serverName); // your IP address with path or Domain name with URL path
int httpResponseCode = http.GET(); // send HTTP POST request
String payload = "{}";
if (httpResponseCode>0)
{
Serial.print ("HTTP Response code: ");
Serial.println (httpResponseCode);
payload = http.getString ();
}
else {
Serial.print( "Error code: ");
Serial.println (httpResponseCode);
}
http.end (); // free microcontroller resources
return payload;
}
void rainbowScaleMeter (){
if (millis () - runTime >= 100) // originally 500 = delay
{
runTime = millis ();
if( tesmod==0)
{
reading = 99;
}
if( tesmod==1)
{
reading = tempRainbowgauge*2; // important: here ring is seeded with value
}
int xpos = 10, ypos = 240, gap = 100; // position of upper ring and proportion
ringMeter (reading,0,100, (rGaugePos_x+15),(rGaugePos_y+17),ringmeterRadius,ringlabel[0],GREEN2RED);
tesmod = 1;
}
}
int ringMeter(int value,int vmin,int vmax,int x,int y,int r, char *units, byte scheme){
// Minimum value of r is about 52 before value text intrudes on ring
// drawing the text first is an option
x += r; y += r; // calculate coordinates of center of ring
int w = r / 3; // width of outer ring is 1/4 of radius
int angle = 150; // half the sweep angle of the meter (300 degrees)
int v = map (value, vmin, vmax, -angle, angle); // map the value to an angle v
byte seg = 3; // segments are 3 degrees wide = 100 segments for 300 degrees
byte inc = 6; // draw segments every 3 degrees, increase to 6 for segmented ring
int colour = BLUE; // variable to save "value" text color from scheme and set default
for (int i = -angle+inc/2; i < angle-inc/2; i += inc) // draw color blocks every increment degrees
{
float sx = cos((i - 90) * DEG2RAD); // calculate pair of coordinates for segment start
float sy = sin((i - 90) * DEG2RAD);
uint16_t x0 = sx * (r - w) + x;
uint16_t y0 = sy * (r - w) + y;
uint16_t x1 = sx * r + x;
uint16_t y1 = sy * r + y;
float sx2 = cos((i + seg - 90) * DEG2RAD); // calculate pair of coordinates for segment end
float sy2 = sin((i + seg - 90) * DEG2RAD);
int x2 = sx2 * (r - w) + x;
int y2 = sy2 * (r - w) + y;
int x3 = sx2 * r + x;
int y3 = sy2 * r + y;
if (i < v)
{ // fill in coloured segments with 2 triangles
switch (scheme)
{
case 0: colour = RED; break; // fixed color
case 1: colour = GREEN; break; // fixed color
case 2: colour = BLUE; break; // fixed colour
case 3: colour = rainbow(map(i, -angle, angle, 0, 127)); break; // full spectrum blue to red
case 4: colour = rainbow(map(i, -angle, angle, 70, 127)); break; // green to red (high temperature etc)
case 5: colour = rainbow(map(i, -angle, angle, 127, 63)); break; // red to green (low battery etc)
default: colour = BLUE; break; // fixed color
}
tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
}
else // fill in blank segments
{
tft.fillTriangle(x0, y0, x1, y1, x2, y2, SCALE1); // color of the unoccupied ring scale
tft.fillTriangle(x1, y1, x2, y2, x3, y3, SCALE0); // color of the unoccupied ring scale
}
}
tft.fillRect (t,y,72,15,BLACK);
tft.setTextSize (2);
if (tempRainbowgauge<-9.9) t = 33;
if (tempRainbowgauge>-9.9) t = 38;
if (tempRainbowgauge > 0 ) t = 50;
if (tempRainbowgauge >9.9) t = 42;
tft.setCursor (t+15,y);
tft.setTextColor (GREEN);
tft.print (tempRainbowgauge,1);
return 0;
}
unsigned int rainbow (byte value) { // value is expected to be in range 0-127
// value is converted to a spectrum color from 0 = blue through to 127 = red
byte red = 0; // red is the top 5 bits of a 16 bit colour value
byte green = 0; // green is the middle 6 bits
byte blue = 0; // blue is the bottom 5 bits
byte quadrant = value / 32;
if (quadrant == 0)
{
blue = 31;
green = 2 * (value % 32);
red = 0;
}
if (quadrant == 1)
{
blue = 31 - (value % 32);
green = 63;
red = 0;
}
if (quadrant == 2)
{
blue = 0;
green = 63;
red = value % 32;
}
if (quadrant == 3)
{
blue = 0;
green = 63 - 2 * (value % 32);
red = 31;
}
return (red << 11) + (green << 5) + blue;
}
float sineWave(int phase) {
return sin(phase * 0.0174532925);
}
void drawSaleSmallGauge (){
j = 270; // start point of cirle segment
do {
angleCircle = (j* DEG2RAD); // angle expressed in radians - 1 degree = 0,01745331 radians
edge_x1 = (center_x1+4 + (radius_s*cos (angleCircle))); // scale - note the 4 pixels offset in x
edge_y1 = (center_y1 + (radius_s*sin (angleCircle))); // scale
edge_x1_out = (center_x1+4 + ((radius_s+edgemarkerLength)*cos (angleCircle))); // scale - note the 4 pixels offset in x
edge_y1_out = (center_y1 + ((radius_s+edgemarkerLength)*sin (angleCircle))); // scale
tft.drawLine (edge_x1, edge_y1, edge_x1_out, edge_y1_out,MAGENTA);
j = j+6;
}
while (j<356); // end of circle segment
}
void needleMeter (){
tft.drawLine (pivotNeedle_x, pivotNeedle_y, needle_x_old, needle_y_old, 0); // remove old needle by overwritig in white
// if (101 > hum_02 < 0) {
angleNeedle = (420*DEG2RAD - 1.5*hum_02*DEG2RAD); // contains a 1.5 stretch factor to expand 60 percentage points over 90 degrees of scale
if (angleNeedle > 6.28) angleNeedle = 6.28; // prevents the needle from ducking below horizontal
needle_x = (pivotNeedle_x + ((needleLength)*cos (angleNeedle))); // calculate x coordinate needle point
needle_y = (pivotNeedle_y + ((needleLength)*sin (angleNeedle))); // calculate y coordinate needle point
needle_x_old = needle_x; // remember previous needle position
needle_y_old = needle_y;
tft.drawLine (pivotNeedle_x, pivotNeedle_y, needle_x, needle_y,MAGENTA);
// }
Serial.println("-----------------------------");
Serial.println(pivotNeedle_x);
Serial.println(pivotNeedle_y);
Serial.println(needle_x);
Serial.println(needle_y);
Serial.println(hum_01);
Serial.println(hum_02);
tft.fillCircle (pivotNeedle_x, pivotNeedle_y, 2, MAGENTA); // restore needle pivot
}
void circleSegmentMeter (){
tft.fillCircle (pivotHumcircle__x, pivotHumcircle__y,34,BLACK);
fillSegment (pivotHumcircle__x, pivotHumcircle__y, 0, (360*hum_02/100), 34, TFT_RED); // draw pie chart segment
tft.drawCircle (pivotHumcircle__x, pivotHumcircle__y,35,YELLOW); // correct for faulty pixels circle edge
tft.setCursor (pivotHumcircle__x+10, pivotHumcircle__y-5);
tft.setTextColor (TFT_YELLOW);
tft.setTextSize (1);
tft.print ("hum");
tft.setCursor (pivotHumcircle__x+10, pivotHumcircle__y+5);
tft.print (hum_02);
tft.print ("%");
tft.setTextSize (2);
}
// ######################################################################################
// # circle segment meter - draw segments #
// ######################################################################################
// x,y == coords of centre of circle
// startAngle = 0 - 359
// subAngle = 0 - 360 = subtended angle
// r = radius
// colour = 16 bit colour value
int fillSegment (int x, int y, int startAngle, int subAngle, int r, unsigned int colour)
{
float sx = cos((startAngle - 90) * DEG2RAD); // calculate first pair of coordinates for segment start
float sy = sin((startAngle - 90) * DEG2RAD);
uint16_t x1 = sx * r + x;
uint16_t y1 = sy * r + y;
for (int i = startAngle; i < startAngle + subAngle; i++) // draw color blocks every inc degrees
{
int x2 = cos((i + 1 - 90) * DEG2RAD) * r + x; // calculate pair of coordinates for segment end
int y2 = sin((i + 1 - 90) * DEG2RAD) * r + y;
tft.fillTriangle(x1, y1, x2, y2, x, y, colour);
x1 = x2; // copy segment end to segment start for next segment
y1 = y2;
}
return 0;
}
void compassGauge (){
tft.drawCircle ((compassPivot_x+40), (compassPivot_y+40), compass_r+11, BLUE); // outer circle compass
tft.drawCircle ((compassPivot_x+40), (compassPivot_y+40), compass_r+2, CYAN); // inner circle compass - pointer fits in here
tft.setTextSize (1);
tft.setCursor (compassPivot_x+38, compassPivot_y+4);
tft.setTextColor (WHITE, BLACK);
tft.print ("N");
tft.setCursor (compassPivot_x+38, compassPivot_y+69);
tft.print ("S");
tft.setCursor (compassPivot_x+71, compassPivot_y+35);
tft.print ("E");
tft.setCursor (compassPivot_x+5, compassPivot_y+35);
tft.print ("W");
}
void compassPointer (){
tft.fillTriangle (c_x1_old,c_y1_old,c_x2_old,c_y2_old,c_x4_old,c_y4_old,BLACK); // remove old compass pointer by overwritig in white
tft.fillTriangle (c_x1_old,c_y1_old,c_x3_old,c_y3_old,c_x4_old,c_y4_old,BLACK); // remove old compass pointer by overwritig in white
compassAngle = ((windDir_01-90)*DEG2RAD);
c_x1 = ((compassPivot_x+40) + (compass_r * cos (compassAngle))); // calculate coordinates compass pointer tip
c_y1 = ((compassPivot_y+40) + (compass_r * sin (compassAngle)));
c_x2 = ((compassPivot_x+40) + (compass_r * cos (compassAngle + 163*DEG2RAD))); // calculate coordinates compass pointer base point
c_y2 = ((compassPivot_y+40) + (compass_r * sin (compassAngle + 163*DEG2RAD)));
c_x3 = ((compassPivot_x+40) + (compass_r * cos (compassAngle - 163*DEG2RAD))); // calculate coordinates compass pointer base point
c_y3 = ((compassPivot_y+40) + (compass_r * sin (compassAngle - 163*DEG2RAD)));
c_x4 = ((compassPivot_x+40) + ((compass_r-4) * cos (compassAngle - 180*DEG2RAD))); // calculate coordinates compass pointer base point
c_y4 = ((compassPivot_y+40) + ((compass_r-4) * sin (compassAngle - 180*DEG2RAD)));
c_x1_old = c_x1; c_x2_old = c_x2; c_x3_old = c_x3; c_x4_old = c_x4;
c_y1_old = c_y1; c_y2_old = c_y2; c_y3_old = c_y3; c_y4_old = c_y4;
tft.fillTriangle (c_x1, c_y1,c_x3, c_y3, c_x4, c_y4, 0x3A72); // print the new pointer to display
tft.fillTriangle (c_x1, c_y1,c_x2, c_y2, c_x4, c_y4, BLUE); // print the new pointer to display
}
void windSectorReporter (){
h = 0;
if (windDir_01 <22.5) h = 0;
if (windDir_01> 22.5) h = 1;
if (windDir_01> 67.5) h = 2;
if (windDir_01>112.5) h = 3;
if (windDir_01>157.5) h = 4;
if (windDir_01>202.5) h = 5;
if (windDir_01>247.5) h = 6;
if (windDir_01>292.5) h = 7;
if (windDir_01>337.5) h = 8;
tft.fillRect (208,51,16,8,BLACK);
tft.setCursor (210,51);
tft.print (sector[h]);
}