/* ESP32 Weather Display using an EPD 7.5" 800x480 Display, obtains data from Open Weather Map, decodes and then displays it. #################################################################################################################################### This software, the ideas and concepts is Copyright (c) David Bird 2018. All rights to this software are reserved. Any redistribution or reproduction of any part or all of the contents in any form is prohibited other than the following: 1. You may print or download to a local hard disk extracts for your personal and non-commercial use only. 2. You may copy the content to individual third parties for their personal use, but only if you acknowledge the author David Bird as the source of the material. 3. You may not, except with my express written permission, distribute or commercially exploit the content. 4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes. The above copyright ('as annotated') notice and this permission notice shall be included in all copies or substantial portions of the Software and where the software use is visible to an end-user. THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT. FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. See more at http://www.dsbird.org.uk */ #define SCK 14 #define MISO 2 #define MOSI 15 #define CS 13 #include #include "owm_credentials.h" // See 'owm_credentials' tab and enter your OWM API key and set the Wifi SSID and PASSWORD #include // https://github.com/bblanchon/ArduinoJson needs version v6 or above #include // Built-in #include "time.h" // Built-in #include // Built-in #define ENABLE_GxEPD2_display 1 #include //#include #include #include "epaper_fonts.h" #include "forecast_record.h" #include "lang.h" // Localisation (English) //#include "lang_cz.h" // Localisation (Czech) //#include "lang_fr.h" // Localisation (French) //#include "lang_gr.h" // Localisation (German) //#include "lang_it.h" // Localisation (Italian) //#include "lang_nl.h" //#include "lang_pl.h" // Localisation (Polish) #include #include //pins for receiving data string RX TX SoftwareSerial mySerial(3, 1); // RX=3, TX #define SCREEN_WIDTH 800 // Set for landscape mode #define SCREEN_HEIGHT 480 enum alignment {LEFT, RIGHT, CENTER}; File myFile; File file; int i; int rec[50]; static const uint8_t EPD_BUSY = 4; // to EPD BUSY static const uint8_t EPD_CS = 5; // to EPD CS static const uint8_t EPD_RST = 16; // to EPD RST static const uint8_t EPD_DC = 17; // to EPD DC static const uint8_t EPD_SCK = 18; // to EPD CLK static const uint8_t EPD_MISO = 2; // Master-In Slave-Out not used, as no data from display static const uint8_t EPD_MOSI = 23; // to EPD DIN int vol=0; // Connections for e.g. LOLIN D32 //static const uint8_t EPD_BUSY = 4; // to EPD BUSY //static const uint8_t EPD_CS = 5; // to EPD CS //static const uint8_t EPD_RST = 16; // to EPD RST //static const uint8_t EPD_DC = 17; // to EPD DC //static const uint8_t EPD_SCK = 18; // to EPD CLK //static const uint8_t EPD_MISO = 19; // Master-In Slave-Out not used, as no data from display //static const uint8_t EPD_MOSI = 23; // to EPD DIN // Connections for e.g. Waveshare ESP32 e-Paper Driver Board //static const uint8_t EPD_BUSY = 25; //static const uint8_t EPD_CS = 15; //static const uint8_t EPD_RST = 26; //static const uint8_t EPD_DC = 27; //static const uint8_t EPD_SCK = 13; //static const uint8_t EPD_MISO = 12; // Master-In Slave-Out not used, as no data from display //static const uint8_t EPD_MOSI = 14; GxEPD2_BW display(GxEPD2_750_T7(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // B/W display //GxEPD2_3C display(GxEPD2_750(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // 3-colour displays // use GxEPD_BLACK or GxEPD_WHITE or GxEPD_RED or GxEPD_YELLOW depending on display type U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; // Select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall // Using fonts: // u8g2_font_helvB08_tf // u8g2_font_helvB10_tf // u8g2_font_helvB12_tf // u8g2_font_helvB14_tf // u8g2_font_helvB18_tf // u8g2_font_helvB24_tf //################ VERSION ########################################### String version = "16.11"; // Programme version, see change log at end //################ VARIABLES ########################################### boolean LargeIcon = true, SmallIcon = false; #define Large 17 // For icon drawing, needs to be odd number for best effect #define Small 6 // For icon drawing, needs to be odd number for best effect String Time_str, Date_str; // strings to hold time and received weather data int wifi_signal, CurrentHour = 0, CurrentMin = 0, CurrentSec = 0; long StartTime = 0; //################ PROGRAM VARIABLES and OBJECTS ################ #define max_readings 24 Forecast_record_type WxConditions[1]; Forecast_record_type WxForecast[max_readings]; #include "common.h" #define autoscale_on true #define autoscale_off false #define barchart_on true #define barchart_off false float pressure_readings[max_readings] = {0}; float temperature_readings[max_readings] = {0}; float humidity_readings[max_readings] = {0}; float rain_readings[max_readings] = {0}; float snow_readings[max_readings] = {0}; int Temp[4]={0,0,0,0}; int Moisture[4]={0,0,0,0}; int Ec[4]={0,0,0,0}; String Syear,Smonth,Sday,Shour,Sminute,Ssecond; long SleepDuration = 1; // Sleep time in minutes, aligned to the nearest minute boundary, so if 30 will always update at 00 or 30 past the hour int WakeupTime = 7; // Don't wakeup until after 07:00 to save battery power int SleepTime = 23; // Sleep after (23+1) 00:00 to save battery power void setup() { //Serial.begin(Baud Rate, Data Protocol, Txd pin, Rxd pin); StartTime = millis(); Serial.begin(115200); Serial.print("Initializing SD card..."); mySerial.begin(9600); // Define and start serial monitor delay(1000);//Receiver.begin(115200, SERIAL_8N1, Receiver_Txd_pin, Receiver_Rxd_pin); // Define and start Receiver serial port // if (StartWiFi() == WL_CONNECTED);{// && SetupTime() == true) { // InitialiseDisplay(); // Give screen time to initialise by getting weather data! // byte Attempts = 1; // bool RxWeather = false, RxForecast = false; // WiFiClient client; // wifi client object // while ((RxWeather == false || RxForecast == false) && Attempts <= 2) { // Try up-to 2 time for Weather and Forecast data // if (RxWeather == false) RxWeather = obtain_wx_data(client, "weather"); // if (RxForecast == false) RxForecast = obtain_wx_data(client, "forecast"); // Attempts++; // } // // if (RxWeather && RxForecast) { // Only if received both Weather or Forecast proceed // StopWiFi(); // Reduces power consumption ser(); // Serial.println("Wriye"); display.display(false); // Full screen update mode // } // BeginSleep(); } void loop() { //ser(); } void BeginSleep() { display.powerOff(); long SleepTimer = (SleepDuration * 10 - ((CurrentMin % SleepDuration) * 60 + CurrentSec)); //Some ESP32 are too fast to maintain accurate time esp_sleep_enable_timer_wakeup((SleepTimer+20) * 1000000LL); // Added extra 20-secs of sleep to allow for slow ESP32 RTC timers #ifdef BUILTIN_LED pinMode(BUILTIN_LED, INPUT); // If it's On, turn it off and some boards use GPIO-5 for SPI-SS, which remains low after screen use digitalWrite(BUILTIN_LED, HIGH); #endif Serial.println("Entering " + String(SleepTimer) + "-secs of sleep time"); Serial.println("Awake for : " + String((millis() - StartTime) / 1000.0, 3) + "-secs"); Serial.println("Starting deep-sleep period..."); esp_deep_sleep_start(); // Sleep for e.g. 30 minutes } void ser(void) { int Tag=0; int Volume=0; float FlowRate=0; int light=0; int k=0; while(Tag==0) { String LoRaData; Serial.println("LoRaData7"); delay(5000); k=k+1; // for(int ll=0;ll<3;ll++){ while (mySerial.available()) // Wait for the Receiver to get the characters {LoRaData = mySerial.readString(); Serial.println(LoRaData); delay(1000); //String LoRaData = mySerial.readString();// Display the Receivers characters Serial.println("Taking data"); // Get readingID, temperature and soil moisture int pos1 = LoRaData.indexOf('!'); int pos2 = LoRaData.indexOf('/'); int pos3 = LoRaData.indexOf('&'); int pos4 = LoRaData.indexOf('$'); int pos5 = LoRaData.indexOf('*'); int pos6 = LoRaData.indexOf('['); int pos7 = LoRaData.indexOf(']'); int pos8 = LoRaData.indexOf('|'); int pos9 = LoRaData.indexOf('^'); int pos10 = LoRaData.indexOf('?'); int pos11 = LoRaData.indexOf('a'); int pos12 = LoRaData.indexOf('b'); int pos13 = LoRaData.indexOf('c'); int pos14 = LoRaData.indexOf('d'); int pos15 = LoRaData.indexOf('e'); int pos16 = LoRaData.indexOf('f'); int pos17 = LoRaData.indexOf('g'); int pos18 = LoRaData.indexOf('h'); int pos19 = LoRaData.indexOf('i'); int pos20 = LoRaData.indexOf('j'); int pos21 = LoRaData.indexOf('k'); int pos22 = LoRaData.indexOf('l'); int pos23 = LoRaData.indexOf('m'); // int pos24 = LoRaData.indexOf('n'); // int pos25 = LoRaData.indexOf('o'); // int pos26 = LoRaData.indexOf('p'); // int pos27 = LoRaData.indexOf('q'); // int pos28 = LoRaData.indexOf('r'); // int pos29 = LoRaData.indexOf('s'); // // String TxRxMessage = String(sensorID) + "!" + String(readingID[i]) + "/" + String(Ec[i]) + "&" + String(temperature[i])+"$" + String(moisture[i])+ "*" + String(AirTemp[i])+ "[" + String(AirHumi[i])+ "]" + String(FlowRate)+ "|"+ String(Volume)+"^"; // 1!40/1&0$387*0[38]145.00|23.00^1!40/1&0$387*0[38]145.00|23.00^1! // String Message = String(sensorID) + "!" + String(readingID[i]) + "/" + String(avgN) + "&" + String(avgt)+"$" + String(avgm)+ "*" + String(avgat)+ "[" + String(avgah)+ "]" + // String(FlowRate)+ "|"+ String(Volume)+"^"+"5"+"?"+ String(Ec[0]) + "a" + String(temperature[0])+"b" + String(moisture[0])+ "c"+ String(Ec[1]) + "d" + String(temperature[1])+"e" + // String(moisture[1])+ "f"+ String(Ec[2]) + "g" + String(temperature[2])+"h" + String(moisture[2])+ "i"+ String(Ec[3]) + "j" + String(temperature[3])+"k" + String(moisture[3])+ "l"; String sensorID = LoRaData.substring(0, pos1); String SreadingID = LoRaData.substring(pos1 +1, pos2); String SEc = LoRaData.substring(pos2 +1, pos3); String Stemperature = LoRaData.substring(pos3 +1, pos4); String Smoisture = LoRaData.substring(pos4 +1, pos5); String SAirTemp = LoRaData.substring(pos5 +1, pos6); String SAirHumi = LoRaData.substring(pos6 +1, pos7); String SFlowRate = LoRaData.substring(pos7 +1, pos8); String SVolume = LoRaData.substring(pos8 +1, pos9); String STag = LoRaData.substring(pos9 +1, pos10); String SEc0 = LoRaData.substring(pos10 +1, pos11); String Stemperature0 = LoRaData.substring(pos11 +1, pos12); String Smoisture0 = LoRaData.substring(pos12 +1, pos13); String SEc1 = LoRaData.substring(pos13 +1, pos14); String Stemperature1 = LoRaData.substring(pos14 +1, pos15); String Smoisture1 = LoRaData.substring(pos15 +1, pos16); String SEc2 = LoRaData.substring(pos16 +1, pos17); String Stemperature2 = LoRaData.substring(pos17 +1, pos18); String Smoisture2 = LoRaData.substring(pos18 +1, pos19); String SEc3 = LoRaData.substring(pos19 +1, pos20); String Stemperature3 = LoRaData.substring(pos20 +1, pos21); String Smoisture3 = LoRaData.substring(pos21 +1, pos22); String Slight = LoRaData.substring(pos22 +1, pos23); // Syear = LoRaData.substring(pos23 +1, pos24); // Smonth = LoRaData.substring(pos24 +1, pos25); // Sday = LoRaData.substring(pos25 +1, pos26); // Shour = LoRaData.substring(pos26 +1, pos27); // Sminute = LoRaData.substring(pos27 +1, pos28); // Ssecond = LoRaData.substring(pos28 +1, pos29); // + String(now.year())+ "n"+ String(now.month())+ "o"+ String(now.day())+ "p"+ String(now.hour())+ "q"+ String(now.minute())+ "r"+ String(now.second())+ "s"; String SI=sensorID; //dont know why we have to do this int SID = SI.toInt(); int readingID = SreadingID.toInt(); int AvEc = SEc.toInt(); int moisture = Smoisture.toInt(); int temperature = Stemperature.toInt(); int AirTemp = SAirTemp.toInt(); int AirHumi = SAirHumi.toInt(); FlowRate = SFlowRate.toFloat(); Volume = SVolume.toInt(); light = Slight.toInt(); Ec[0] = SEc0.toInt(); Ec[1] = SEc1.toInt(); Ec[2] = SEc2.toInt(); Ec[3] = SEc3.toInt(); // yea = Syear.toInt(); // mont = Smonth.toInt(); // da = Sday.toInt(); // hou = Shour.toInt(); // minut = Sminute.toInt(); // secon = Ssecond.toInt(); // SPIClass spi = SPIClass(VSPI); spi.begin(SCK, MISO, MOSI, CS); if (!SD.begin(CS,spi,80000000)) { Serial.println("initialization failed!"); } Serial.println("initialization done."); Serial.println("AVerage"); Serial.println(AvEc); myFile = SD.open("/ELC.txt", FILE_WRITE); if (myFile) { Serial.print("Writing EC to test.txt..."); myFile.println(AvEc); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening EC0.txt"); } Serial.println("DATA PRINTING"); int Elc[100],ff; int count=0; file = SD.open("/ELC.txt"); while(file.available()){ Elc[count]=file.parseInt(); Serial.print(Elc[count]); Serial.print(" "); Serial.println(count); count++; //Serial.print( Elc[count]); //Serial.println("DATA "); } file.close(); myFile = SD.open("/MOI.txt", FILE_WRITE); if (myFile) { Serial.print("Writing Moisture.txt..."); myFile.println(moisture); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening MOI.txt"); } Serial.println("DATA PRINTING"); int Moi[100]; int count1=0; file = SD.open("/MOI.txt"); while(file.available()){ Moi[count1]=file.parseInt(); Serial.print(Moi[count1]); Serial.print(" "); Serial.println(count1); count1++; //Serial.print( Elc[count]); //Serial.println("DATA "); } file.close(); myFile = SD.open("/TEM.txt", FILE_WRITE); if (myFile) { Serial.print("Writing Temperature.txt..."); myFile.println(temperature); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening TEM.txt"); } Serial.println("DATA PRINTING"); int Tem[100]; int count2=0; file = SD.open("/TEM.txt"); while(file.available()){ Tem[count2]=file.parseInt(); Serial.print(Tem[count2]); Serial.print(" "); Serial.println(count2); count2++; //Serial.print( Elc[count]); //Serial.println("DATA "); } file.close(); //int Nitr=8; myFile = SD.open("/NIT.txt", FILE_WRITE); if (myFile) { Serial.print("Writing Nitrogen.txt..."); myFile.println(AvEc*2/30); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening TEM.txt"); } Serial.println("DATA PRINTING"); int Nit[100]; int count3=0; file = SD.open("/NIT.txt"); while(file.available()){ Nit[count3]=file.parseInt(); Serial.print(Nit[count3]); Serial.print(" "); Serial.println(count3); count3++; //Serial.print( Elc[count]); //Serial.println("DATA "); } file.close(); //for Air Temp myFile = SD.open("/AIRTEMP.txt", FILE_WRITE); if (myFile) { Serial.print("Writing Air Temp.txt..."); myFile.println(AirTemp); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening AIRTEMP.txt"); } Serial.println("DATA PRINTING"); int AirT[100]; int count4=0; file = SD.open("/AIRTEMP.txt"); while(file.available()){ AirT[count4]=file.parseInt(); Serial.print(AirT[count4]); Serial.print(" "); Serial.println(count4); count4++; } file.close(); //for Air Humidity myFile = SD.open("/AIRHUMI.txt", FILE_WRITE); if (myFile) { Serial.print("Writing Air Temp.txt..."); myFile.println(AirHumi); myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening AIRTEMP.txt"); } Serial.println("DATA PRINTING"); int AirH[100]; int count5=0; file = SD.open("/AIRHUMI.txt"); while(file.available()){ AirH[count5]=file.parseInt()+count5; Serial.print(AirH[count5]); Serial.print(" "); Serial.println(count5); count5++; } file.close(); SPI.end(); InitialiseDisplay(); Temp[0] = Stemperature0.toInt(); Temp[1] = Stemperature1.toInt(); Temp[2] = Stemperature2.toInt(); Temp[3] = Stemperature3.toInt(); Moisture[0] = Smoisture0.toInt(); Moisture[1] = Smoisture1.toInt(); Moisture[2] = Smoisture2.toInt(); Moisture[3] = Smoisture3.toInt(); // int SID = SI.toInt(); Tag = STag.toInt(); Serial.print("AirTemp: "); Serial.println(AirTemp); Serial.print("Air Humi: "); Serial.println(AirHumi); // Serial.print("pH: "); Serial.println(pH); Serial.print("Moisture: "); Serial.println(moisture); Serial.print("FlowRate: "); Serial.println(FlowRate); Serial.print("Volume: "); Serial.println(Volume); Serial.print("Tag: "); Serial.println(Tag); delay(2000); // DisplayGeneralInfoSection(); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(6, 2, "[Version: " + version + "]", LEFT); // Programme version drawString(SCREEN_WIDTH / 2, 3, "KUAgriIoT "+City, CENTER); // u8g2Fonts.setFont(u8g2_font_helvB14_tf); // drawString(487, 194,DayTime, CENTER); display.drawRect(1, 1, 796, 478, GxEPD_BLACK); // precipitation outline display.drawRect(1, 245, 204, 100, GxEPD_BLACK); // precipitation outline DisplayDisplayNitrogenSection(108, 295,AvEc*2/30, 38); DisplayDisplayMoistureSection(108, 120,moisture, 81,12); DisplayVolumeSection(454, 20, Volume, FlowRate, 137, 100); DisplayAirSection(590, 20, AirTemp, AirHumi, 137, 100,81); // DisplayConditionsSection1(290, 100 + 49, WxConditions[0].Icon, LargeIcon,Volume); DisplayPHSection(711, 20, 125, 100); // DisplayForecastWeather1(204, 245, 0); // DisplayForecastWeather1(204, 245, 1); // DisplayForecastWeather1(204, 245, 2); // DisplayForecastWeather1(204, 245, 3); // int gwidth = 120, gheight = 72; int gx = (SCREEN_WIDTH - gwidth * 4) / 5 + 5; int gy = 375; int gap = (gwidth + gx)*0.80; float dd[5]={0,9, 4, 0, 8}; float dd1[5]={0,9, 6, 0, 5}; // DrawGraph1(gx + 0 * gap, gy, gwidth, gheight, 25, 50, "Av Soil Moisture" , Moi, count1-1, autoscale_on, barchart_off); // DrawGraph1(gx + 1 * gap, gy, gwidth, gheight, 0, 50, "Av Soil Temp" , Tem, count2-1, autoscale_off, barchart_off); // DrawGraph1(gx + 2 * gap, gy, gwidth, gheight, 0, 50, "Av Soil Nitrogen" , Nit, count3-1, autoscale_off, barchart_off); // DrawGraph1(gx + 3 * gap, gy, gwidth, gheight, 606, 650, "EC" , Elc, count-1, autoscale_on, barchart_off); // DrawGraph1(gx + 4 * gap, gy, gwidth, gheight, 10, 100, "" , AirT,count4-1 , autoscale_off, barchart_off); // DrawGraph1(gx + 4 * gap, gy, gwidth, gheight, 10, 100, "Air Temp Humidity" , AirH, count5-1, autoscale_off, barchart_off); // DisplayForecastSection(217, 245) } Serial.println(k); if (k==2) ESP.restart(); } } uint8_t StartWiFi() { Serial.print("\r\nConnecting to: "); Serial.println(String(ssid)); IPAddress dns(8, 8, 8, 8); // Google DNS WiFi.disconnect(); WiFi.mode(WIFI_STA); // switch off AP WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); WiFi.begin(ssid, password); unsigned long start = millis(); uint8_t connectionStatus; bool AttemptConnection = true; while (AttemptConnection) { connectionStatus = WiFi.status(); if (millis() > start + 15000) { // Wait 15-secs maximum AttemptConnection = false; } if (connectionStatus == WL_CONNECTED || connectionStatus == WL_CONNECT_FAILED) { AttemptConnection = false; } delay(50); } if (connectionStatus == WL_CONNECTED) { wifi_signal = WiFi.RSSI(); // Get Wifi Signal strength now, because the WiFi will be turned off to save power! Serial.println("WiFi connected at: " + WiFi.localIP().toString()); } else Serial.println("WiFi connection *** FAILED ***"); return connectionStatus; } //######################################################################################### void StopWiFi() { WiFi.disconnect(); WiFi.mode(WIFI_OFF); } //######################################################################################### void InitialiseDisplay() { display.init(115200, true, 2, false); // init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode) // display.init(); for older Waveshare HAT's SPI.end(); SPI.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); u8g2Fonts.begin(display); // connect u8g2 procedures to Adafruit GFX u8g2Fonts.setFontMode(1); // use u8g2 transparent mode (this is default) u8g2Fonts.setFontDirection(0); // left to right (this is default) u8g2Fonts.setForegroundColor(GxEPD_BLACK); // apply Adafruit GFX color u8g2Fonts.setBackgroundColor(GxEPD_WHITE); // apply Adafruit GFX color u8g2Fonts.setFont(u8g2_font_helvB10_tf); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall display.fillScreen(GxEPD_WHITE); display.setFullWindow(); } //######################################################################################### void DisplayWeather() { // 7.5" e-paper display is 800x480 resolution //delay(4000); //DisplayGeneralInfoSection(); // Top line of the display //DisplayDisplayWindSection(108, 146, WxConditions[0].Winddir, WxConditions[0].Windspeed, 81); DisplayMainWeatherSection(300, 100); // Centre section of display for Location, temperature, Weather report, current Wx Symbol and wind direction //DisplayForecastSection(217, 245); // 3hr forecast boxes DisplayAstronomySection(0, 245); // Astronomy section Sun rise/set, Moon phase and Moon icon DisplayStatusSection(690, 215, wifi_signal); // Wi-Fi signal strength and Battery voltage ser(); } //######################################################################################### void DisplayGeneralInfoSection() { u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(6, 2, "[Version: " + version + "]", LEFT); // Programme version drawString(SCREEN_WIDTH / 2, 3, City, CENTER); u8g2Fonts.setFont(u8g2_font_helvB14_tf); drawString(487, 194, Date_str, CENTER); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(500, 225, Time_str, CENTER); display.drawLine(0, 18, SCREEN_WIDTH - 3, 18, GxEPD_BLACK); display.drawLine(0, 4, SCREEN_WIDTH - 3, 4, GxEPD_BLACK); } //######################################################################################### void DisplayMainWeatherSection(int x, int y) { // display.drawRect(x-67, y-65, 140, 182, GxEPD_BLACK); display.drawLine(0, 38, SCREEN_WIDTH - 3, 38, GxEPD_BLACK); //DisplayConditionsSection1(x + 3, y + 49, WxConditions[0].Icon, LargeIcon); //DisplayTemperatureSection(x + 154, y - 81, 137, 100); // DisplayPressureSection(x + 281, y - 81, WxConditions[0].Pressure, WxConditions[0].Trend, 137, 100); // DisplayPrecipitationSection(x + 411, y - 81, 137, 100); DisplayForecastTextSection(x + 97, y + 20, 409, 65); } //######################################################################################### void DisplayDisplayWindSection(int x, int y, float angle, float windspeed, int Cradius) { arrow(x, y, Cradius - 22, angle, 18, 33); // Show wind direction on outer circle of width and length u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - Cradius - 41, TXT_WIND_SPEED_DIRECTION, CENTER); int dxo, dyo, dxi, dyi; display.drawLine(0, 18, 0, y + Cradius + 37, GxEPD_BLACK); display.drawCircle(x, y, Cradius, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius + 1, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius * 0.7, GxEPD_BLACK); // Draw compass inner circle for (float a = 0; a < 360; a = a + 22.5) { dxo = Cradius * cos((a - 90) * PI / 180); dyo = Cradius * sin((a - 90) * PI / 180); if (a == 45) drawString(dxo + x + 12, dyo + y - 12, TXT_NE, CENTER); if (a == 135) drawString(dxo + x + 7, dyo + y + 6, TXT_SE, CENTER); if (a == 225) drawString(dxo + x - 18, dyo + y, TXT_SW, CENTER); if (a == 315) drawString(dxo + x - 18, dyo + y - 12, TXT_NW, CENTER); dxi = dxo * 0.9; dyi = dyo * 0.9; display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); dxo = dxo * 0.7; dyo = dyo * 0.7; dxi = dxo * 0.9; dyi = dyo * 0.9; display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); } drawString(x, y - Cradius - 12, TXT_N, CENTER); drawString(x, y + Cradius + 6, TXT_S, CENTER); drawString(x - Cradius - 12, y - 3, TXT_W, CENTER); drawString(x + Cradius + 10, y - 3, TXT_E, CENTER); drawString(x - 2, y - 43, WindDegToDirection(angle), CENTER); drawString(x + 6, y + 30, String(angle, 0) + "°", CENTER); u8g2Fonts.setFont(u8g2_font_helvB18_tf); drawString(x - 12, y - 3, String(windspeed, 1), CENTER); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y + 12, (Units == "M" ? "m/s" : "mph"), CENTER); } //######################################################################################### String WindDegToDirection(float winddirection) { if (winddirection >= 348.75 || winddirection < 11.25) return TXT_N; if (winddirection >= 11.25 && winddirection < 33.75) return TXT_NNE; if (winddirection >= 33.75 && winddirection < 56.25) return TXT_NE; if (winddirection >= 56.25 && winddirection < 78.75) return TXT_ENE; if (winddirection >= 78.75 && winddirection < 101.25) return TXT_E; if (winddirection >= 101.25 && winddirection < 123.75) return TXT_ESE; if (winddirection >= 123.75 && winddirection < 146.25) return TXT_SE; if (winddirection >= 146.25 && winddirection < 168.75) return TXT_SSE; if (winddirection >= 168.75 && winddirection < 191.25) return TXT_S; if (winddirection >= 191.25 && winddirection < 213.75) return TXT_SSW; if (winddirection >= 213.75 && winddirection < 236.25) return TXT_SW; if (winddirection >= 236.25 && winddirection < 258.75) return TXT_WSW; if (winddirection >= 258.75 && winddirection < 281.25) return TXT_W; if (winddirection >= 281.25 && winddirection < 303.75) return TXT_WNW; if (winddirection >= 303.75 && winddirection < 326.25) return TXT_NW; if (winddirection >= 326.25 && winddirection < 348.75) return TXT_NNW; return "?"; } //######################################################################################### void DisplayForecastTextSection(int x, int y , int fwidth, int fdepth) { display.drawRect(x - 6, y - 3, fwidth, fdepth, GxEPD_BLACK); // forecast text outline u8g2Fonts.setFont(u8g2_font_helvB14_tf); String Wx_Description = WxConditions[0].Main0; if (WxConditions[0].Forecast0 != "") Wx_Description += " (" + WxConditions[0].Forecast0; if (WxConditions[0].Forecast1 != "") Wx_Description += ", " + WxConditions[0].Forecast1; if (WxConditions[0].Forecast2 != "") Wx_Description += ", " + WxConditions[0].Forecast2; if (Wx_Description.indexOf("(") > 0) Wx_Description += ")"; int MsgWidth = 43; // Using proportional fonts, so be aware of making it too wide! if (Language == "DE") drawStringMaxWidth(x, y + 23, MsgWidth, Wx_Description, LEFT); // Leave German text in original format, 28 character screen width at this font size else drawStringMaxWidth(x, y + 23, MsgWidth, TitleCase(Wx_Description), LEFT); // 28 character screen width at this font size u8g2Fonts.setFont(u8g2_font_helvB10_tf); } //######################################################################################### void DisplayForecastWeather(int x, int y, int index) { int fwidth = 73; x = x + fwidth * index; display.drawRect(x, y, fwidth - 1, 81, GxEPD_BLACK); display.drawLine(x, y + 16, x + fwidth - 3, y + 16, GxEPD_BLACK); DisplayConditionsSection(x + fwidth / 2, y + 43, WxForecast[index].Icon, SmallIcon); //WxForecast[index].Icon drawString(x + fwidth / 2, y + 4, String(ConvertUnixTime(WxForecast[index].Dt + WxConditions[0].Timezone).substring(0,5)), CENTER); drawString(x + fwidth / 2 + 12, y + 66, String(WxForecast[index].High, 0) + "°/" + String(WxForecast[index].Low, 0) + "DD", CENTER); // orignial° } //######################################################################################### void DisplayPressureSection(int x, int y, float pressure, String slope, int pwidth, int pdepth) { display.drawRect(x - 56, y - 1, pwidth, pdepth, GxEPD_BLACK); // pressure outline u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 8, y + 5, TXT_PRESSURE, CENTER); String slope_direction = TXT_PRESSURE_STEADY; if (slope == "+") slope_direction = TXT_PRESSURE_RISING; if (slope == "-") slope_direction = TXT_PRESSURE_FALLING; display.drawRect(x + 40, y + 78, 41, 21, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB24_tf); if (Units == "I") drawString(x - 22, y + 55, String(pressure, 2), CENTER); // "Imperial" else drawString(x - 22, y + 55, String(pressure, 0), CENTER); // "Metric" u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 59, y + 83, (Units == "M" ? "hPa" : "in"), CENTER); drawString(x - 3, y + 83, slope_direction, CENTER); } //######################################################################################### void DisplayPrecipitationSection(int x, int y, int pwidth, int pdepth) { display.drawRect(x - 48, y - 1, pwidth, pdepth, GxEPD_BLACK); // precipitation outline u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 25, y + 5, "pH", CENTER); u8g2Fonts.setFont(u8g2_font_helvB12_tf); if (WxForecast[1].Rainfall >= 0.005) { // Ignore small amounts drawString(x - 25, y + 40, String(WxForecast[1].Rainfall, 2) + (Units == "M" ? "mm" : "in"), LEFT); // Only display rainfall total today if > 0 addraindrop(x + 58, y + 40, 7); } if (WxForecast[1].Snowfall >= 0.005) // Ignore small amounts drawString(x - 25, y + 71, String(WxForecast[1].Snowfall, 2) + (Units == "M" ? "mm" : "in") + " **", LEFT); // Only display snowfall total today if > 0 if (WxForecast[1].Pop >= 0.005) // Ignore small amounts drawString(x + 2, y + 81, String(WxForecast[1].Pop*100, 0) + "%", LEFT); // Only display pop if > 0 } //######################################################################################### void DisplayAstronomySection(int x, int y) { display.drawRect(x, y + 16, 216, 65, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB08_tf); // drawString(x + 4, y + 24, ConvertUnixTime(WxConditions[0].Sunrise + WxConditions[0].Timezone).substring(0, 5) + " " + TXT_SUNRISE, LEFT); // drawString(x + 4, y + 44, ConvertUnixTime(WxConditions[0].Sunset + WxConditions[0].Timezone).substring(0, 5) + " " + TXT_SUNSET, LEFT); time_t now = time(NULL); struct tm * now_utc = gmtime(&now); const int day_utc = now_utc->tm_mday; const int month_utc = now_utc->tm_mon + 1; const int year_utc = now_utc->tm_year + 1900; drawString(x + 4, y + 64, MoonPhase(day_utc, month_utc, year_utc, Hemisphere), LEFT); DrawMoon(x + 137, y, day_utc, month_utc, year_utc, Hemisphere); } //######################################################################################### void DrawMoon(int x, int y, int dd, int mm, int yy, String hemisphere) { const int diameter = 47; double Phase = NormalizedMoonPhase(dd, mm, yy); hemisphere.toLowerCase(); if (hemisphere == "south") Phase = 1 - Phase; // Draw dark part of moon display.fillCircle(x + diameter - 1, y + diameter, diameter / 2 + 1, GxEPD_BLACK); const int number_of_lines = 90; for (double Ypos = 0; Ypos <= number_of_lines / 2; Ypos++) { double Xpos = sqrt(number_of_lines / 2 * number_of_lines / 2 - Ypos * Ypos); // Determine the edges of the lighted part of the moon double Rpos = 2 * Xpos; double Xpos1, Xpos2; if (Phase < 0.5) { Xpos1 = -Xpos; Xpos2 = Rpos - 2 * Phase * Rpos - Xpos; } else { Xpos1 = Xpos; Xpos2 = Xpos - 2 * Phase * Rpos + Rpos; } // Draw light part of moon double pW1x = (Xpos1 + number_of_lines) / number_of_lines * diameter + x; double pW1y = (number_of_lines - Ypos) / number_of_lines * diameter + y; double pW2x = (Xpos2 + number_of_lines) / number_of_lines * diameter + x; double pW2y = (number_of_lines - Ypos) / number_of_lines * diameter + y; double pW3x = (Xpos1 + number_of_lines) / number_of_lines * diameter + x; double pW3y = (Ypos + number_of_lines) / number_of_lines * diameter + y; double pW4x = (Xpos2 + number_of_lines) / number_of_lines * diameter + x; double pW4y = (Ypos + number_of_lines) / number_of_lines * diameter + y; display.drawLine(pW1x, pW1y, pW2x, pW2y, GxEPD_WHITE); display.drawLine(pW3x, pW3y, pW4x, pW4y, GxEPD_WHITE); } display.drawCircle(x + diameter - 1, y + diameter, diameter / 2, GxEPD_BLACK); } //######################################################################################### String MoonPhase(int d, int m, int y, String hemisphere) { int c, e; double jd; int b; if (m < 3) { y--; m += 12; } ++m; c = 365.25 * y; e = 30.6 * m; jd = c + e + d - 694039.09; /* jd is total days elapsed */ jd /= 29.53059; /* divide by the moon cycle (29.53 days) */ b = jd; /* int(jd) -> b, take integer part of jd */ jd -= b; /* subtract integer part to leave fractional part of original jd */ b = jd * 8 + 0.5; /* scale fraction from 0-8 and round by adding 0.5 */ b = b & 7; /* 0 and 8 are the same phase so modulo 8 for 0 */ if (hemisphere == "south") b = 7 - b; if (b == 0) return TXT_MOON_NEW; // New; 0% illuminated if (b == 1) return TXT_MOON_WAXING_CRESCENT; // Waxing crescent; 25% illuminated if (b == 2) return TXT_MOON_FIRST_QUARTER; // First quarter; 50% illuminated if (b == 3) return TXT_MOON_WAXING_GIBBOUS; // Waxing gibbous; 75% illuminated if (b == 4) return TXT_MOON_FULL; // Full; 100% illuminated if (b == 5) return TXT_MOON_WANING_GIBBOUS; // Waning gibbous; 75% illuminated if (b == 6) return TXT_MOON_THIRD_QUARTER; // Third quarter; 50% illuminated if (b == 7) return TXT_MOON_WANING_CRESCENT; // Waning crescent; 25% illuminated return ""; } //######################################################################################### String MoonPhase1(int d, int m, int y, String hemisphere,float Flow) { int c, e; double jd; int b; if (m < 3) { y--; m += 12; } ++m; c = 365.25 * y; e = 30.6 * m; jd = c + e + d - 694039.09; /* jd is total days elapsed */ jd /= 29.53059; /* divide by the moon cycle (29.53 days) */ b = jd; /* int(jd) -> b, take integer part of jd */ jd -= b; /* subtract integer part to leave fractional part of original jd */ b = jd * 8 + 0.5; /* scale fraction from 0-8 and round by adding 0.5 */ b = b & 7; /* 0 and 8 are the same phase so modulo 8 for 0 */ if (Flow ==0) return TXT_MOON_FULL; if (Flow >0) return TXT_MOON_NEW; // if (hemisphere == "south") b = 7 - b; // if (b == 0) return TXT_MOON_NEW; // New; 0% illuminated // if (b == 1) return TXT_MOON_WAXING_CRESCENT; // Waxing crescent; 25% illuminated // if (b == 2) return TXT_MOON_FIRST_QUARTER; // First quarter; 50% illuminated // if (b == 3) return TXT_MOON_WAXING_GIBBOUS; // Waxing gibbous; 75% illuminated // if (b == 4) return TXT_MOON_FULL; // Full; 100% illuminated // if (b == 5) return TXT_MOON_WANING_GIBBOUS; // Waning gibbous; 75% illuminated // if (b == 6) return TXT_MOON_THIRD_QUARTER; // Third quarter; 50% illuminated // if (b == 7) return TXT_MOON_WANING_CRESCENT; // Waning crescent; 25% illuminated return ""; } //######################################################################################### void DisplayForecastSection(int x, int y) { u8g2Fonts.setFont(u8g2_font_helvB08_tf); int f = 0; do { // DisplayForecastWeather(x, y, f); f++; } while (f <= 7); // Pre-load temporary arrays with with data - because C parses by reference int r = 1; do { if (Units == "I") pressure_readings[r] = WxForecast[r].Pressure * 0.02953; else pressure_readings[r] = WxForecast[r].Pressure; if (Units == "I") rain_readings[r] = WxForecast[r].Rainfall * 0.0393701; else rain_readings[r] = WxForecast[r].Rainfall; if (Units == "I") snow_readings[r] = WxForecast[r].Snowfall * 0.0393701; else snow_readings[r] = WxForecast[r].Snowfall; temperature_readings[r] = WxForecast[r].Temperature; humidity_readings[r] = WxForecast[r].Humidity; r++; } while (r <= max_readings); int gwidth = 150, gheight = 72; int gx = (SCREEN_WIDTH - gwidth * 4) / 5 + 5; int gy = 375; int gap = gwidth + gx; u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(SCREEN_WIDTH / 2, gy - 40, TXT_FORECAST_VALUES, CENTER); // Based on a graph height of 60 u8g2Fonts.setFont(u8g2_font_helvB08_tf); // (x,y,width,height,MinValue, MaxValue, Title, Data Array, AutoScale, ChartMode) DrawGraph(gx + 0 * gap, gy, gwidth, gheight, 900, 1050, Units == "M" ? TXT_PRESSURE_HPA : TXT_PRESSURE_IN, pressure_readings, max_readings, autoscale_on, barchart_off); DrawGraph(gx + 1 * gap, gy, gwidth, gheight, 10, 30, Units == "M" ? TXT_TEMPERATURE_C : TXT_TEMPERATURE_F, temperature_readings, max_readings, autoscale_on, barchart_off); DrawGraph(gx + 2 * gap, gy, gwidth, gheight, 0, 100, TXT_HUMIDITY_PERCENT, humidity_readings, max_readings, autoscale_off, barchart_off); if (SumOfPrecip(rain_readings, max_readings) >= SumOfPrecip(snow_readings, max_readings)) DrawGraph(gx + 3 * gap + 5, gy, gwidth, gheight, 0, 30, Units == "M" ? TXT_RAINFALL_MM : TXT_RAINFALL_IN, rain_readings, max_readings, autoscale_on, barchart_on); else DrawGraph(gx + 3 * gap + 5, gy, gwidth, gheight, 0, 30, Units == "M" ? TXT_SNOWFALL_MM : TXT_SNOWFALL_IN, snow_readings, max_readings, autoscale_on, barchart_on); } //######################################################################################### void DisplayConditionsSection(int x, int y, String IconName, bool IconSize) { Serial.println("Icon name: " + IconName); if (IconName == "01d" || IconName == "01n") Sunny(x, y, IconSize, IconName); else if (IconName == "02d" || IconName == "02n") MostlySunny(x, y, IconSize, IconName); else if (IconName == "03d" || IconName == "03n") Cloudy(x, y, IconSize, IconName); else if (IconName == "04d" || IconName == "04n") MostlySunny(x, y, IconSize, IconName); else if (IconName == "09d" || IconName == "09n") ChanceRain(x, y, IconSize, IconName); else if (IconName == "10d" || IconName == "10n") Rain(x, y, IconSize, IconName); else if (IconName == "11d" || IconName == "11n") Tstorms(x, y, IconSize, IconName); else if (IconName == "13d" || IconName == "13n") Snow(x, y, IconSize, IconName); else if (IconName == "50d") Haze(x, y, IconSize, IconName); else if (IconName == "50n") Fog(x, y, IconSize, IconName); else Nodata(x, y, IconSize, IconName); if (IconSize == LargeIcon) { display.drawRect(x - 86, y - 131, 173, 228, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - 125, TXT_CONDITIONS, CENTER); u8g2Fonts.setFont(u8g2_font_helvB14_tf); drawString(x - 25, y + 70, String(WxConditions[0].Humidity, 0) + "%", CENTER); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 35, y + 80, "RH", CENTER); if (WxConditions[0].Visibility > 0) Visibility(x - 62, y - 87, String(WxConditions[0].Visibility) + "M"); if (WxConditions[0].Cloudcover > 0) CloudCover(x + 35, y - 87, WxConditions[0].Cloudcover); } } void DisplayConditionsSection1(int x, int y, String IconName, bool IconSize,int Volu) { Serial.println("Icon name: " + IconName); // if (IconName == "01d" || IconName == "01n") Sunny(x, y, IconSize, IconName); // else if (IconName == "02d" || IconName == "02n") MostlySunny(x, y, IconSize, IconName); // else if (IconName == "03d" || IconName == "03n") Cloudy(x, y, IconSize, IconName); // else if (IconName == "04d" || IconName == "04n") MostlySunny(x, y, IconSize, IconName); // else if (IconName == "09d" || IconName == "09n") ChanceRain(x, y, IconSize, IconName); // else if (IconName == "10d" || IconName == "10n") Rain(x, y, IconSize, IconName); // else if (IconName == "11d" || IconName == "11n") Tstorms(x, y, IconSize, IconName); // else if (IconName == "13d" || IconName == "13n") Snow(x, y, IconSize, IconName); // else if (IconName == "50d") Haze(x, y, IconSize, IconName); // else if (IconName == "50n") Fog(x, y, IconSize, IconName); // else Nodata(x, y, IconSize, IconName); // if (IconSize == LargeIcon) { display.drawRect(x - 86, y - 131, 173, 228, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - 125, "Flow", CENTER); // u8g2Fonts.setFont(u8g2_font_helvB14_tf); // drawString(x - 25, y + 70, String(WxConditions[0].Humidity, 0) + "%", CENTER); // // if (WxConditions[0].Visibility > 0) Visibility(x - 62, y - 87, String(WxConditions[0].Visibility) + "M"); // if (WxConditions[0].Cloudcover > 0) CloudCover(x + 35, y - 87, WxConditions[0].Cloudcover); if (Volu>0) { Sunny(x, y, IconSize, IconName); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 30, y + 80, "Pump is ON ", CENTER); } else { Haze(x, y, IconSize, IconName); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 35, y + 80, "Pump is OFF", CENTER); } //} } //######################################################################################### void arrow(int x, int y, int asize, float aangle, int pwidth, int plength) { float dx = (asize + 28) * cos((aangle - 90) * PI / 180) + x; // calculate X position float dy = (asize + 28) * sin((aangle - 90) * PI / 180) + y; // calculate Y position float x1 = 0; float y1 = plength; float x2 = pwidth / 2; float y2 = pwidth / 2; float x3 = -pwidth / 2; float y3 = pwidth / 2; float angle = aangle * PI / 180; float xx1 = x1 * cos(angle) - y1 * sin(angle) + dx; float yy1 = y1 * cos(angle) + x1 * sin(angle) + dy; float xx2 = x2 * cos(angle) - y2 * sin(angle) + dx; float yy2 = y2 * cos(angle) + x2 * sin(angle) + dy; float xx3 = x3 * cos(angle) - y3 * sin(angle) + dx; float yy3 = y3 * cos(angle) + x3 * sin(angle) + dy; display.fillTriangle(xx1, yy1, xx3, yy3, xx2, yy2, GxEPD_BLACK); } //######################################################################################### void DisplayStatusSection(int x, int y, int rssi) { display.drawRect(x - 35, y - 32, 145, 61, GxEPD_BLACK); display.drawLine(x - 35, y - 17, x - 35 + 145, y - 17, GxEPD_BLACK); display.drawLine(x - 35 + 146 / 2, y - 18, x - 35 + 146 / 2, y - 32, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - 29, TXT_WIFI, CENTER); drawString(x + 68, y - 30, TXT_POWER, CENTER); DrawRSSI(x - 10, y + 6, rssi); DrawBattery(x + 58, y + 6);; } //######################################################################################### void DrawRSSI(int x, int y, int rssi) { int WIFIsignal = 0; int xpos = 1; for (int _rssi = -100; _rssi <= rssi; _rssi = _rssi + 20) { if (_rssi <= -20) WIFIsignal = 20; // <-20dbm displays 5-bars if (_rssi <= -40) WIFIsignal = 16; // -40dbm to -21dbm displays 4-bars if (_rssi <= -60) WIFIsignal = 12; // -60dbm to -41dbm displays 3-bars if (_rssi <= -80) WIFIsignal = 8; // -80dbm to -61dbm displays 2-bars if (_rssi <= -100) WIFIsignal = 4; // -100dbm to -81dbm displays 1-bar display.fillRect(x + xpos * 6, y - WIFIsignal, 5, WIFIsignal, GxEPD_BLACK); xpos++; } display.fillRect(x, y - 1, 5, 1, GxEPD_BLACK); drawString(x + 6, y + 6, String(rssi) + "dBm", CENTER); } //######################################################################################### boolean SetupTime() { configTime(gmtOffset_sec, daylightOffset_sec, ntpServer, "time.nist.gov"); //(gmtOffset_sec, daylightOffset_sec, ntpServer) setenv("TZ", Timezone, 1); //setenv()adds the "TZ" variable to the environment with a value TimeZone, only used if set to 1, 0 means no change tzset(); // Set the TZ environment variable delay(100); bool TimeStatus = UpdateLocalTime(); return TimeStatus; } //######################################################################################### boolean UpdateLocalTime() { struct tm timeinfo; char time_output[30], day_output[30], update_time[30]; while (!getLocalTime(&timeinfo, 30000)) { // Wait for 10-sec for time to synchronise Serial.println("Failed to obtain time"); return false; } CurrentHour = timeinfo.tm_hour; CurrentMin = timeinfo.tm_min; CurrentSec = timeinfo.tm_sec; //See http://www.cplusplus.com/reference/ctime/strftime/ //Serial.println(&timeinfo, "%a %b %d %Y %H:%M:%S"); // Displays: Saturday, June 24 2017 14:05:49 if (Units == "M") { if ((Language == "CZ") || (Language == "DE") || (Language == "PL") || (Language == "NL")){ sprintf(day_output, "%s, %02u. %s %04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); // day_output >> So., 23. Juni 2019 << } else { sprintf(day_output, "%s %02u-%s-%04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); } strftime(update_time, sizeof(update_time), "%H:%M:%S", &timeinfo); // Creates: '14:05:49' sprintf(time_output, "%s %s", TXT_UPDATED, update_time); } else { strftime(day_output, sizeof(day_output), "%a %b-%d-%Y", &timeinfo); // Creates 'Sat May-31-2019' strftime(update_time, sizeof(update_time), "%r", &timeinfo); // Creates: '02:05:49pm' sprintf(time_output, "%s %s", TXT_UPDATED, update_time); } Date_str = day_output; Time_str = time_output; return true; } //######################################################################################### void DrawBattery(int x, int y) { uint8_t percentage = 100; float voltage = analogRead(35) / 4096.0 * 7.46; if (voltage > 1 ) { // Only display if there is a valid reading Serial.println("Voltage = " + String(voltage)); percentage = 2836.9625 * pow(voltage, 4) - 43987.4889 * pow(voltage, 3) + 255233.8134 * pow(voltage, 2) - 656689.7123 * voltage + 632041.7303; if (voltage >= 4.20) percentage = 100; if (voltage <= 3.50) percentage = 0; display.drawRect(x + 15, y - 12, 19, 10, GxEPD_BLACK); display.fillRect(x + 34, y - 10, 2, 5, GxEPD_BLACK); display.fillRect(x + 17, y - 10, 15 * percentage / 100.0, 6, GxEPD_BLACK); drawString(x + 10, y - 11, String(percentage) + "%", RIGHT); drawString(x + 13, y + 5, String(voltage, 2) + "v", CENTER); } } //######################################################################################### // Symbols are drawn on a relative 10x10grid and 1 scale unit = 1 drawing unit void addcloud(int x, int y, int scale, int linesize) { //Draw cloud outer display.fillCircle(x - scale * 3, y, scale, GxEPD_BLACK); // Left most circle display.fillCircle(x + scale * 3, y, scale, GxEPD_BLACK); // Right most circle display.fillCircle(x - scale, y - scale, scale * 1.4, GxEPD_BLACK); // left middle upper circle display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75, GxEPD_BLACK); // Right middle upper circle display.fillRect(x - scale * 3 - 1, y - scale, scale * 6, scale * 2 + 1, GxEPD_BLACK); // Upper and lower lines //Clear cloud inner display.fillCircle(x - scale * 3, y, scale - linesize, GxEPD_WHITE); // Clear left most circle display.fillCircle(x + scale * 3, y, scale - linesize, GxEPD_WHITE); // Clear right most circle display.fillCircle(x - scale, y - scale, scale * 1.4 - linesize, GxEPD_WHITE); // left middle upper circle display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75 - linesize, GxEPD_WHITE); // Right middle upper circle display.fillRect(x - scale * 3 + 2, y - scale + linesize - 1, scale * 5.9, scale * 2 - linesize * 2 + 2, GxEPD_WHITE); // Upper and lower lines } //######################################################################################### 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 addrain(int x, int y, int scale, bool IconSize) { if (IconSize == SmallIcon) scale *= 1.34; for (int d = 0; d < 4; d++) { addraindrop(x + scale * (7.8 - d * 1.95) - scale * 5.2, y + scale * 2.1 - scale / 6, scale / 1.6); } } //######################################################################################### void addsnow(int x, int y, int scale, bool IconSize) { int dxo, dyo, dxi, dyi; for (int flakes = 0; flakes < 5; flakes++) { for (int i = 0; i < 360; i = i + 45) { dxo = 0.5 * scale * cos((i - 90) * 3.14 / 180); dxi = dxo * 0.1; dyo = 0.5 * scale * sin((i - 90) * 3.14 / 180); dyi = dyo * 0.1; display.drawLine(dxo + x + flakes * 1.5 * scale - scale * 3, dyo + y + scale * 2, dxi + x + 0 + flakes * 1.5 * scale - scale * 3, dyi + y + scale * 2, GxEPD_BLACK); } } } //######################################################################################### void addtstorm(int x, int y, int scale) { y = y + scale / 2; for (int i = 0; i < 5; i++) { display.drawLine(x - scale * 4 + scale * i * 1.5 + 0, y + scale * 1.5, x - scale * 3.5 + scale * i * 1.5 + 0, y + scale, GxEPD_BLACK); if (scale != Small) { display.drawLine(x - scale * 4 + scale * i * 1.5 + 1, y + scale * 1.5, x - scale * 3.5 + scale * i * 1.5 + 1, y + scale, GxEPD_BLACK); display.drawLine(x - scale * 4 + scale * i * 1.5 + 2, y + scale * 1.5, x - scale * 3.5 + scale * i * 1.5 + 2, y + scale, GxEPD_BLACK); } display.drawLine(x - scale * 4 + scale * i * 1.5, y + scale * 1.5 + 0, x - scale * 3 + scale * i * 1.5 + 0, y + scale * 1.5 + 0, GxEPD_BLACK); if (scale != Small) { display.drawLine(x - scale * 4 + scale * i * 1.5, y + scale * 1.5 + 1, x - scale * 3 + scale * i * 1.5 + 0, y + scale * 1.5 + 1, GxEPD_BLACK); display.drawLine(x - scale * 4 + scale * i * 1.5, y + scale * 1.5 + 2, x - scale * 3 + scale * i * 1.5 + 0, y + scale * 1.5 + 2, GxEPD_BLACK); } display.drawLine(x - scale * 3.5 + scale * i * 1.4 + 0, y + scale * 2.5, x - scale * 3 + scale * i * 1.5 + 0, y + scale * 1.5, GxEPD_BLACK); if (scale != Small) { display.drawLine(x - scale * 3.5 + scale * i * 1.4 + 1, y + scale * 2.5, x - scale * 3 + scale * i * 1.5 + 1, y + scale * 1.5, GxEPD_BLACK); display.drawLine(x - scale * 3.5 + scale * i * 1.4 + 2, y + scale * 2.5, x - scale * 3 + scale * i * 1.5 + 2, y + scale * 1.5, 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 addfog(int x, int y, int scale, int linesize, bool IconSize) { if (IconSize == SmallIcon) { y -= 10; linesize = 1; } for (int i = 0; i < 6; i++) { display.fillRect(x - scale * 3, y + scale * 1.5, scale * 6, linesize, GxEPD_BLACK); display.fillRect(x - scale * 3, y + scale * 2.0, scale * 6, linesize, GxEPD_BLACK); display.fillRect(x - scale * 3, y + scale * 2.5, scale * 6, linesize, GxEPD_BLACK); } } //######################################################################################### void Sunny(int x, int y, bool IconSize, String IconName) { int scale = Small; if (IconSize == LargeIcon) scale = Large; else y = y - 3; // Shift up small sun icon if (IconName.endsWith("n")) addmoon(x, y + 3, scale, IconSize); scale = scale * 1.6; addsun(x, y, scale, IconSize); } //######################################################################################### void MostlySunny(int x, int y, bool IconSize, String IconName) { int scale = Small, linesize = 3, offset = 5; if (IconSize == LargeIcon) { scale = Large; offset = 10; } if (scale == Small) linesize = 1; if (IconName.endsWith("n")) addmoon(x, y + offset, scale, IconSize); addcloud(x, y + offset, scale, linesize); addsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, IconSize); } //######################################################################################### void MostlyCloudy(int x, int y, bool IconSize, String IconName) { int scale = Small, linesize = 3; if (IconSize == LargeIcon) { scale = Large; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x, y, scale, linesize); addsun(x - scale * 1.8, y - scale * 1.8, scale, IconSize); addcloud(x, y, scale, linesize); } //######################################################################################### void Cloudy(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); linesize = 1; addcloud(x, y, scale, linesize); } else { y += 10; if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x + 30, y - 45, 5, linesize); // Cloud top right addcloud(x - 20, y - 30, 7, linesize); // Cloud top left addcloud(x, y, scale, linesize); // Main cloud } } //######################################################################################### void Rain(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x, y, scale, linesize); addrain(x, y, scale, IconSize); } //######################################################################################### void ExpectRain(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addsun(x - scale * 1.8, y - scale * 1.8, scale, IconSize); addcloud(x, y, scale, linesize); addrain(x, y, scale, IconSize); } //######################################################################################### void ChanceRain(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addsun(x - scale * 1.8, y - scale * 1.8, scale, IconSize); addcloud(x, y, scale, linesize); addrain(x, y, scale, IconSize); } //######################################################################################### void Tstorms(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x, y, scale, linesize); addtstorm(x, y, scale); } //######################################################################################### void Snow(int x, int y, bool IconSize, String IconName) { int scale = Large, linesize = 3; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x, y, scale, linesize); addsnow(x, y, scale, IconSize); } //######################################################################################### void Fog(int x, int y, bool IconSize, String IconName) { int linesize = 3, scale = Large; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addcloud(x, y - 5, scale, linesize); addfog(x, y - 5, scale, linesize, IconSize); } //######################################################################################### void Haze(int x, int y, bool IconSize, String IconName) { int linesize = 3, scale = Large; if (IconSize == SmallIcon) { scale = Small; linesize = 1; } if (IconName.endsWith("n")) addmoon(x, y, scale, IconSize); addsun(x, y - 5, scale * 1.4, IconSize); addfog(x, y - 5, scale * 1.4, linesize, IconSize); } //######################################################################################### void CloudCover(int x, int y, int CCover) { addcloud(x - 9, y - 3, Small * 0.5, 2); // Cloud top left addcloud(x + 3, y - 3, Small * 0.5, 2); // Cloud top right addcloud(x, y, Small * 0.5, 2); // Main cloud u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 15, y - 5, String(CCover) + "%", LEFT); } //######################################################################################### void Visibility(int x, int y, String Visi) { y = y - 3; // float start_angle = 0.52, end_angle = 2.61; int r = 10; for (float i = start_angle; i < end_angle; i = i + 0.05) { display.drawPixel(x + r * cos(i), y - r / 2 + r * sin(i), GxEPD_BLACK); display.drawPixel(x + r * cos(i), 1 + y - r / 2 + r * sin(i), GxEPD_BLACK); } start_angle = 3.61; end_angle = 5.78; for (float i = start_angle; i < end_angle; i = i + 0.05) { display.drawPixel(x + r * cos(i), y + r / 2 + r * sin(i), GxEPD_BLACK); display.drawPixel(x + r * cos(i), 1 + y + r / 2 + r * sin(i), GxEPD_BLACK); } display.fillCircle(x, y, r / 4, GxEPD_BLACK); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 12, y - 3, Visi, LEFT); } //######################################################################################### void addmoon(int x, int y, int scale, bool IconSize) { if (IconSize == LargeIcon) { display.fillCircle(x - 62, y - 68, scale, GxEPD_BLACK); display.fillCircle(x - 43, y - 68, scale * 1.6, GxEPD_WHITE); } else { display.fillCircle(x - 25, y - 15, scale, GxEPD_BLACK); display.fillCircle(x - 18, y - 15, scale * 1.6, GxEPD_WHITE); } } //######################################################################################### void Nodata(int x, int y, bool IconSize, String IconName) { if (IconSize == LargeIcon) u8g2Fonts.setFont(u8g2_font_helvB24_tf); else u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x - 3, y - 10, "?", CENTER); u8g2Fonts.setFont(u8g2_font_helvB08_tf); } //######################################################################################### /* (C) D L BIRD This function will draw a graph on a ePaper/TFT/LCD display using data from an array containing data to be graphed. The variable 'max_readings' determines the maximum number of data elements for each array. Call it with the following parametric data: x_pos-the x axis top-left position of the graph y_pos-the y-axis top-left position of the graph, e.g. 100, 200 would draw the graph 100 pixels along and 200 pixels down from the top-left of the screen width-the width of the graph in pixels height-height of the graph in pixels Y1_Max-sets the scale of plotted data, for example 5000 would scale all data to a Y-axis of 5000 maximum data_array1 is parsed by value, externally they can be called anything else, e.g. within the routine it is called data_array1, but externally could be temperature_readings auto_scale-a logical value (TRUE or FALSE) that switches the Y-axis autoscale On or Off barchart_on-a logical value (TRUE or FALSE) that switches the drawing mode between barhcart and line graph barchart_colour-a sets the title and graph plotting colour If called with Y!_Max value of 500 and the data never goes above 500, then autoscale will retain a 0-500 Y scale, if on, the scale increases/decreases to match the data. auto_scale_margin, e.g. if set to 1000 then autoscale increments the scale by 1000 steps. */ void DrawGraph(int x_pos, int y_pos, int gwidth, int gheight, float Y1Min, float Y1Max, String title, float DataArray[], int readings, boolean auto_scale, boolean barchart_mode) { #define auto_scale_margin 0 // Sets the autoscale increment, so axis steps up in units of e.g. 3 #define y_minor_axis 5 // 5 y-axis division markers float maxYscale = -10000; float minYscale = 10000; int last_x, last_y; float x2, y2; if (auto_scale == true) { for (int i = 1; i < readings; i++ ) { if (DataArray[i] >= maxYscale) maxYscale = DataArray[i]; if (DataArray[i] <= minYscale) minYscale = DataArray[i]; } maxYscale = round(maxYscale + auto_scale_margin); // Auto scale the graph and round to the nearest value defined, default was Y1Max Y1Max = round(maxYscale + 0.5); if (minYscale != 0) minYscale = round(minYscale - auto_scale_margin); // Auto scale the graph and round to the nearest value defined, default was Y1Min Y1Min = round(minYscale); } // Draw the graph last_x = x_pos + 1; last_y = y_pos + (Y1Max - constrain(DataArray[1], Y1Min, Y1Max)) / (Y1Max - Y1Min) * gheight; display.drawRect(x_pos, y_pos, gwidth + 3, gheight + 2, GxEPD_BLACK); drawString(x_pos + gwidth / 2 + 6, y_pos - 16, title, CENTER); // Draw the data for (int gx = 1; gx < readings; gx++) { x2 = x_pos + gx * gwidth / (readings - 1) - 1 ; // max_readings is the global variable that sets the maximum data that can be plotted y2 = y_pos + (Y1Max - constrain(DataArray[gx], Y1Min, Y1Max)) / (Y1Max - Y1Min) * gheight + 1; if (barchart_mode) { display.fillRect(x2, y2, (gwidth / readings) - 1, y_pos + gheight - y2 + 2, GxEPD_BLACK); } else { display.drawLine(last_x, last_y, x2, y2, GxEPD_BLACK); } last_x = x2; last_y = y2; } //Draw the Y-axis scale #define number_of_dashes 20 for (int spacing = 0; spacing <= y_minor_axis; spacing++) { for (int j = 0; j < number_of_dashes; j++) { // Draw dashed graph grid lines if (spacing < y_minor_axis) display.drawFastHLine((x_pos + 3 + j * gwidth / number_of_dashes), y_pos + (gheight * spacing / y_minor_axis), gwidth / (2 * number_of_dashes), GxEPD_BLACK); } if ((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing) < 5 || title == TXT_PRESSURE_IN) { drawString(x_pos - 1, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 1), RIGHT); } else { if (Y1Min < 1 && Y1Max < 10) drawString(x_pos - 1, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 1), RIGHT); else drawString(x_pos - 2, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 0), RIGHT); } } for (int i = 0; i <= 2; i++) { drawString(15 + x_pos + gwidth / 3 * i, y_pos + gheight + 3, String(i), LEFT); } drawString(x_pos + gwidth / 2, y_pos + gheight + 14, TXT_DAYS, CENTER); } void DrawGraph1(int x_pos, int y_pos, int gwidth, int gheight, float Y1Min, float Y1Max, String title, int DataArray[], int readings, boolean auto_scale, boolean barchart_mode) { #define auto_scale_margin 0 // Sets the autoscale increment, so axis steps up in units of e.g. 3 #define y_minor_axis 5 // 5 y-axis division markers float maxYscale = -10000; float minYscale = 10000; int last_x, last_y; float x2, y2; if (auto_scale == true) { for (int i = 1; i < readings; i++ ) { if (DataArray[i] >= maxYscale) maxYscale = DataArray[i]; if (DataArray[i] <= minYscale) minYscale = DataArray[i]; } maxYscale = round(maxYscale + auto_scale_margin); // Auto scale the graph and round to the nearest value defined, default was Y1Max Y1Max = round(maxYscale + 0.5); if (minYscale != 0) minYscale = round(minYscale - auto_scale_margin); // Auto scale the graph and round to the nearest value defined, default was Y1Min Y1Min = round(minYscale); } // Draw the graph last_x = x_pos + 1; last_y = y_pos + (Y1Max - constrain(DataArray[1], Y1Min, Y1Max)) / (Y1Max - Y1Min) * gheight; display.drawRect(x_pos, y_pos, gwidth + 3, gheight + 2, GxEPD_BLACK); drawString(x_pos + gwidth / 2 + 6, y_pos - 16, title, CENTER); // Draw the data for (int gx = 1; gx < readings; gx++) { x2 = x_pos + gx * gwidth / (readings - 1) - 1 ; // max_readings is the global variable that sets the maximum data that can be plotted y2 = y_pos + (Y1Max - constrain(DataArray[gx], Y1Min, Y1Max)) / (Y1Max - Y1Min) * gheight + 1; if (barchart_mode) { display.fillRect(x2, y2, (gwidth / readings) - 1, y_pos + gheight - y2 + 2, GxEPD_BLACK); } else { display.drawLine(last_x, last_y, x2, y2, GxEPD_BLACK); } last_x = x2; last_y = y2; } //Draw the Y-axis scale #define number_of_dashes 20 for (int spacing = 0; spacing <= y_minor_axis; spacing++) { for (int j = 0; j < number_of_dashes; j++) { // Draw dashed graph grid lines if (spacing < y_minor_axis) display.drawFastHLine((x_pos + 3 + j * gwidth / number_of_dashes), y_pos + (gheight * spacing / y_minor_axis), gwidth / (2 * number_of_dashes), GxEPD_BLACK); } if ((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing) < 5 || title == TXT_PRESSURE_IN) { drawString(x_pos - 1, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 1), RIGHT); } else { if (Y1Min < 1 && Y1Max < 10) drawString(x_pos - 1, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 1), RIGHT); else drawString(x_pos - 2, y_pos + gheight * spacing / y_minor_axis - 5, String((Y1Max - (float)(Y1Max - Y1Min) / y_minor_axis * spacing + 0.01), 0), RIGHT); } } for (int i = 0; i <= 2; i++) { drawString(15 + x_pos + gwidth / 3 * i, y_pos + gheight + 3, String(i), LEFT); } drawString(x_pos + gwidth / 2, y_pos + gheight + 14, TXT_DAYS, CENTER); } //######################################################################################### void drawString(int x, int y, String text, alignment align) { int16_t x1, y1; //the bounds of x,y and w and h of the variable 'text' in pixels. uint16_t w, h; display.setTextWrap(false); display.getTextBounds(text, x, y, &x1, &y1, &w, &h); if (align == RIGHT) x = x - w; if (align == CENTER) x = x - w / 2; u8g2Fonts.setCursor(x, y + h); u8g2Fonts.print(text); } //######################################################################################### void drawStringMaxWidth(int x, int y, unsigned int text_width, String text, alignment align) { int16_t x1, y1; //the bounds of x,y and w and h of the variable 'text' in pixels. uint16_t w, h; display.getTextBounds(text, x, y, &x1, &y1, &w, &h); if (align == RIGHT) x = x - w; if (align == CENTER) x = x - w / 2; u8g2Fonts.setCursor(x, y); if (text.length() > text_width * 2) { u8g2Fonts.setFont(u8g2_font_helvB10_tf); text_width = 42; y = y - 3; } u8g2Fonts.println(text.substring(0, text_width)); if (text.length() > text_width) { u8g2Fonts.setCursor(x, y + h + 15); String secondLine = text.substring(text_width); secondLine.trim(); // Remove any leading spaces u8g2Fonts.println(secondLine); } } //######################################################################################### //######################################################################################### void DisplayDisplayMoistureSection(int x, int y, float moisture, int Cradius,int fon) { float angle; angle=moisture; int k; if (angle<=50){ for (k=50; k < angle+50+1; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 18, 33); // Show wind direction on outer circle of width and length } } if (angle>50){ for (k=50; k < 100; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 18, 33); // Show wind direction on outer circle of width and length } for (k=0; k < angle-50; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 18, 33); // Show wind direction on outer circle of width and length } } u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - Cradius - 25, "Moisture", CENTER); int dxo, dyo, dxi, dyi; //display.drawLine(0, 18, 0, y + Cradius + 37, GxEPD_BLACK); display.drawCircle(x, y, Cradius, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius + 1, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius * 0.7, GxEPD_BLACK); // Draw compass inner circle for (float a = 0; a < 100; a = a + 12.5) { dxo = Cradius * cos((a*3.6 - 90) * PI / 180); dyo = Cradius * sin((a*3.6 - 90) * PI / 180); if (a == 12.5) drawString(dxo + x + 12, dyo + y - 12, "62.5", CENTER); if (a == 37.5) drawString(dxo + x + 7, dyo + y + 6, "87.5", CENTER); if (a == 62.5) drawString(dxo + x - 18, dyo + y, "12.5", CENTER); if (a == 87.5) drawString(dxo + x - 18, dyo + y - 12, "37.5", CENTER); dxi = dxo * 0.9; dyi = dyo * 0.9; display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); dxo = dxo * 0.7; dyo = dyo * 0.7; dxi = dxo * 0.9; dyi = dyo * 0.9; display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); } drawString(x, y - Cradius - 12, "50", CENTER); drawString(x, y + Cradius + 6, "0", CENTER); drawString(x - Cradius - 12, y - 3, "25", CENTER); drawString(x + Cradius + 10, y - 3, "75", CENTER); drawString(x - 2, y - 43, WindDegToDirection(angle), CENTER); // drawString(x + 6, y + 30, String(angle, 0) + "°", CENTER); if (fon<10) {u8g2Fonts.setFont(u8g2_font_helvB08_tf);} else {u8g2Fonts.setFont(u8g2_font_helvB18_tf);} drawString(x - 12, y - 3, String(moisture, 1), CENTER); u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y + 12, "%", CENTER); } //######################################################################################### void DisplayDisplayNitrogenSection(int x, int y, float nitro, int Cradius) { float angle; angle=nitro; int k; if (angle<=50){ for (k=50; k < angle+50+1; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 12, 20); // Show wind direction on outer circle of width and length } } // if (angle>50){ for (k=50; k < 100; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 12,20); // Show wind direction on outer circle of width and length } for (k=0; k < angle-50; k=k+1) {arrow(x, y, Cradius - 22, k*3.6, 12,20); // Show wind direction on outer circle of width and length } } u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y - Cradius - 25, "Nitrogen", CENTER); // int dxo, dyo, dxi, dyi; // //display.drawLine(0, 18, 0, y + Cradius + 37, GxEPD_BLACK); display.drawCircle(x, y, Cradius, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius + 1, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, Cradius * 0.7, GxEPD_BLACK); // Draw compass inner circle // for (float a = 0; a < 100; a = a + 12.5) { // dxo = Cradius * cos((a*3.6 - 90) * PI / 180); // dyo = Cradius * sin((a*3.6 - 90) * PI / 180); //// if (a == 12.5) drawString(dxo + x + 12, dyo + y - 12, "62.5", CENTER); //// if (a == 37.5) drawString(dxo + x + 7, dyo + y + 6, "87.5", CENTER); //// if (a == 62.5) drawString(dxo + x - 18, dyo + y, "12.5", CENTER); //// if (a == 87.5) drawString(dxo + x - 18, dyo + y - 12, "37.5", CENTER); //// dxi = dxo * 0.9; // dyi = dyo * 0.9; // display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); // dxo = dxo * 0.7; // dyo = dyo * 0.7; // dxi = dxo * 0.9; // dyi = dyo * 0.9; // display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); // } // drawString(x, y - Cradius - 12, "50", CENTER); // drawString(x, y + Cradius + 6, "0", CENTER); // drawString(x - Cradius - 12, y - 3, "25", CENTER); // drawString(x + Cradius + 10, y - 3, "75", CENTER); // drawString(x - 2, y - 43, WindDegToDirection(angle), CENTER); // // drawString(x + 6, y + 30, String(angle, 0) + "°", CENTER); // u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y, String(nitro), CENTER); drawString(x, y + 12, "ppm", CENTER); } // ////******************************************************************************************** void DisplayVolumeSection(int x, int y, float TVolume, float TFlo, int twidth, int tdepth) { delay(2000);// this will never run! display.drawRect(x - 63, y - 1, twidth, tdepth, GxEPD_BLACK); // temp outline u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y + 5, String("Flow Volume"), CENTER); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 10, y + 82, String(TFlo, 0) + " | " + "ml/s", CENTER); // Show forecast high and Low u8g2Fonts.setFont(u8g2_font_helvB24_tf); drawString(x - 12, y + 53, String(TVolume,0) + "", CENTER); // Show Volume discharged in litres u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 43, y + 53, Units == "M" ? "L" : "F", LEFT); } //######################################################################################### void DisplayAirSection(int x, int y, float DAirTemp, float DAirHumi, int twidth, int tdepth, int Cradius) { // delay(2000);// this will never run! display.drawRect(x - 63, y - 1, twidth, tdepth, GxEPD_BLACK); // temp outline u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x, y + 5, String("Air"), CENTER); u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 10, y + 82, String(DAirHumi, 0) + " | " + "RH%", CENTER); // Show forecast high and Low // arrow(x+45, y+20, 20, 180, 18, 33); // angle changed to 90 Show wind direction on outer circle of width and length display.drawTriangle(x + 45, y + 60, x+55, y+25, x+65, y+60,GxEPD_BLACK); display.fillTriangle(x + 45, y + 60, x+55, y+35, x+65, y+60,GxEPD_BLACK); // display.fillTriangle(x+45,y+20,140,9,33); u8g2Fonts.setFont(u8g2_font_helvB24_tf); drawString(x - 22, y + 53, String(DAirTemp,0) + "", CENTER); // Show Volume discharged in litres u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + 6, y + 53, Units == "M" ? "°C" : "F", LEFT); } //######################################################################################### void DisplayForecastWeather1(int x, int y, int index) { int fwidth = 147; x = x + fwidth * index; display.drawRect(x, y, fwidth+1 , 81, GxEPD_BLACK); display.drawLine(x, y + 16, x + fwidth - 3, y + 16, GxEPD_BLACK); // DisplayConditionsSection(x + fwidth / 2, y + 43, WxForecast[index].Icon, SmallIcon); //WxForecast[index].Icon // DisplayConditionsSection(x + fwidth / 2, y + 43, Moisture[index], SmallIcon); //WxForecast[index].Icon u8g2Fonts.setFont(u8g2_font_helvB10_tf); drawString(x + fwidth / 2 + 12, y + 66, String(Moisture[index])+"% " +String(Temp[index])+"°C "+String(Ec[index])+"S", RIGHT);//+ "°/" + String(WxForecast[index].Low, 0) + "DD", CENTER); // orignial° u8g2Fonts.setFont(u8g2_font_helvB08_tf); int SensorNumber = index+1; drawString(x + fwidth / 2, y + 4, String(ConvertUnixTime(WxForecast[index].Dt + WxConditions[0].Timezone).substring(0,5))+" Sen-"+SensorNumber, CENTER); //******NEED TO CHANGE TIME TO CURRENT TIME if (Moisture[index]<20) { display.drawTriangle(x + 45, y + 25, x+55, y+55, x+65, y+25,GxEPD_BLACK); } else { display.drawTriangle(x + 45, y + 55, x+55, y+25, x+65, y+55,GxEPD_BLACK); display.fillTriangle(x + 45, y + 55, x+55, y+25+(55-Moisture[index]), x+65, y+55,GxEPD_BLACK);} //display.fillTriangle(x + 45, y + 60, x+55, y+35, x+65, y+60,GxEPD_BLACK); Serial.print("Moisture"); Serial.print(index); Serial.print("="); Serial.println(Moisture[index]); } //######################################################################################### void DisplayPHSection(int x, int y, int pwidth, int pdepth) { display.drawRect(x - 48, y - 1, pwidth, pdepth, GxEPD_BLACK); // precipitation outline u8g2Fonts.setFont(u8g2_font_helvB08_tf); drawString(x + 25, y + 5, "pH", CENTER); u8g2Fonts.setFont(u8g2_font_helvB12_tf); if (WxForecast[1].Rainfall >= 0.005) { // Ignore small amounts drawString(x - 25, y + 40, String(Moisture[0]) + (Units == "M" ? "mm" : "in"), LEFT); // Only display rainfall total today if > 0 addraindrop(x + 58, y + 40, 7); } if (WxForecast[1].Snowfall >= 0.005) // Ignore small amounts drawString(x - 25, y + 71, String(WxForecast[1].Snowfall, 2) + (Units == "M" ? "mm" : "in") + " **", LEFT); // Only display snowfall total today if > 0 if (WxForecast[1].Pop >= 0.005) // Ignore small amounts drawString(x + 2, y + 81, String(WxForecast[1].Pop*100, 0) + "pH", LEFT); // Only display pop if > 0 }