#include <Arduino.h>
#include <AsyncTCP.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
#include <WiFi.h>
//#include <ESP8266WiFi.h>
#include "SPI.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "DHTesp.h"
#include "NTPClient.h"
#include "WiFiUdp.h"
#include "icons.h"
#include <Fonts/FreeSerif9pt7b.h>
#include "OneButton.h"
#include <EEPROM.h>
#define DEBUG 0
#if DEBUG
#define PRINT(x) Serial.print(x);
#define PRINTLN(x) Serial.println(x);
#else
#define PRINT(x)
#define PRINTLN(x)
#endif
int wi=0;
const int DHT_PIN = 19;
const int buttonPin = 5;
DHTesp dhtSensor;
String openWeatherMapApiKey = "7e04cc3aaf4976b9ffd56f77d83b1fa4";
String temperature_unit = "metric";
String city = "Kyiv";
String jsonBuffer;
float OutTemperature ;
float OutTemperatureMin;
float OutTemperatureMax;
float FeelsTemperature;
int OutHumidity;
String Icon;
int Sun_rise;
int Sun_set;
String endpoint;
JSONVar readings;
JSONVar owmreadings;
String inTemp,outTemp;
String inHum,outHum;
String outTempMax,outTempMin;
String outFeels;
unsigned long lastTimeAcc = 0;
unsigned long OWMapDelay = 60000;
unsigned long lastTime =0;
unsigned long SensorDelay = 5000;
unsigned long OutTime=0;
unsigned long InTime =0;
unsigned long TimeTime=0;
const long utcOffsetInSeconds = 7200;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
String months[12]={"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
String cities[5] = {"Kyiv","Phoenix","London","Amsterdam","Berlin"};
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
// create ezButton object for pin 13;
unsigned long prev_count = 0;
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
OneButton button(buttonPin, true);
unsigned long currentStateStartTime = 0;
const unsigned long stateDuration = 5000; // Тривалість кожного стану у мілісекундах
enum State {
STATE_1,
STATE_2,
STATE_3
};
State currentState = STATE_1;
// Змінні для відстеження того, чи вже було виведено значення для кожного стану
bool printedState1 = false;
bool printedState2 = false;
bool printedState3 = false;
bool timeRun = false;
int pic =0;
int maxtime = 20000;
unsigned long lastSync = 0;
unsigned long lastSecond = 0;
String SunSet;
String SunRise;
bool showClock = false;
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "{}";
if (httpResponseCode > 0) {
PRINTLN("HTTP Response code: ");
PRINTLN(httpResponseCode);
payload = http.getString();
}
else {
PRINTLN("Error code: ");
PRINTLN(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
String timeConverter(long int epoch){
String sun;
int hr=(epoch % 86400L) / 3600;
int min=(epoch % 3600) / 60;
int sec=(epoch % 60);
sun= String(hr)+":"+String(min);
PRINTLN(sun);
return sun;
}
void displaySun() {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(2);
display.setFont(NULL);
display.drawBitmap(20, 10, bitmap9, 24, 24, 1);
display.drawBitmap(20, 40, bitmap10, 24, 24, 1);
display.setCursor(50, 20);
display.print(SunRise);
display.setCursor(50, 45);
display.print(SunSet);
display.setCursor(10, 0);
display.setTextSize(1);
display.print("Sun Rise/Sun Set");
display.drawLine(0, 9, 127, 9, 1);
display.display();
}
void getIndoor()
{
TempAndHumidity data = dhtSensor.getTempAndHumidity();
inTemp=String(data.temperature, 0) + "C";
inHum = String(data.humidity, 0) + "%";
}
void getOutdoor()
{
endpoint = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=" + temperature_unit + "&appid=" + openWeatherMapApiKey;
jsonBuffer = httpGETRequest(endpoint.c_str());
JSONVar myObject = JSON.parse(jsonBuffer);
if (JSON.typeof(myObject) == "undefined") {
PRINTLN("Parsing input failed!");
}
Icon = JSON.stringify(myObject["weather"][0]["icon"]);
if(Icon == "\"01d\"") wi=0;
if(Icon == "\"01n\"") wi=1;
if(Icon == "\"02d\"") wi=2;
if(Icon == "\"02n\"") wi=3;
if(Icon == "\"03d\"") wi=4;
if(Icon == "\"03n\"") wi=5;
if(Icon == "\"04d\"") wi=6;
if(Icon == "\"04n\"") wi=7;
if(Icon == "\"09d\"") wi=8;
if(Icon == "\"09n\"") wi=9;
if(Icon == "\"10d\"") wi=10;
if(Icon == "\"10n\"") wi=11;
if(Icon == "\"11d\"") wi=12;
if(Icon == "\"11n\"") wi=13;
if(Icon == "\"13d\"") wi=14;
if(Icon == "\"13n\"") wi=15;
if(Icon == "\"50d\"") wi=16;
if(Icon == "\"50n\"") wi=17;
OutTemperature = double(myObject["main"]["temp"]);
OutTemperatureMin = double(myObject["main"]["temp_min"]);
OutTemperatureMax = double(myObject["main"]["temp_max"]);
FeelsTemperature = double(myObject["main"]["feels_like"]);
OutHumidity = int(myObject["main"]["humidity"]);
Sun_rise = int(myObject["sys"]["sunrise"]);
Sun_set = int(myObject["sys"]["sunset"]);
int timeOffset = int(myObject["timezone"]);
PRINTLN("Temperature: ");
PRINTLN(OutTemperature);
PRINTLN("OutHumidity: ");
PRINTLN(OutHumidity);
outTemp = String(OutTemperature, 0) +"C";
outHum = String(OutHumidity) + "%";
outFeels = String(FeelsTemperature, 0) +"C";
outTempMin = String(OutTemperatureMin, 0) ;// +"C";
outTempMax = String(OutTemperatureMax, 0) ;// +"C";
outFeels = String(FeelsTemperature, 0) +"C";
SunRise = timeConverter(Sun_rise+utcOffsetInSeconds);
SunSet = timeConverter(Sun_set+utcOffsetInSeconds);
}
void getOutdoor12h ()
{
endpoint = "http://api.openweathermap.org/data/2.5/forecast?q=" + city + "&units=" + temperature_unit +"&cnt=3"+ "&appid=" + openWeatherMapApiKey;
jsonBuffer = httpGETRequest(endpoint.c_str());
JSONVar myObject = JSON.parse(jsonBuffer);
if (JSON.typeof(myObject) == "undefined") {
PRINTLN("Parsing input failed!");
}
}
void displayFeels()
{ display.stopscroll();
display.clearDisplay();
display.setTextSize(2);
display.setCursor(60,15);
display.print(city);
display.setCursor(15,40);
display.print(outFeels);
display.startscrollright(0x00, 0x00);
display.setCursor(0,0);
display.setTextSize(1);
display.println("Feels like");
displayWiFi();
display.display();
}
void displayIndoor()
{ display.stopscroll();
display.clearDisplay();
display.setTextSize(2);
display.setCursor(45,15);
//display.print(city);
display.setCursor(30,44);
display.print(inTemp);
display.println(" "+ inHum);
display.drawBitmap(0, 12, home , 32, 32, WHITE);
display.drawBitmap(40, 12, thermometer_celcius_small , 24, 24, WHITE);
display.drawBitmap(76, 12, humidity_small , 24, 24, WHITE);
display.startscrollright(0x00, 0x00);
display.setCursor(0,0);
display.setTextSize(1);
String header = "Indoor "+city;
display.drawLine(0, 9, 127, 9, 1);
display.println(header);
displayWiFi();
display.display();
}
void displayOutdoor()
{
display.stopscroll();
display.clearDisplay();
display.setTextSize(2);
display.setCursor(45,15);
// display.print(city);
display.setCursor(30,44);
display.print(outTemp);
display.println(" "+ outHum);
display.drawBitmap(0, 12, epd_bitmap_allArray[wi] , 32, 32, WHITE);
display.drawBitmap(40, 12, thermometer_celcius_small , 24, 24, WHITE);
display.drawBitmap(76, 12, humidity_small , 24, 24, WHITE);
display.setCursor(60,14);
display.setTextSize(1);
display.print(outTempMax);
display.setCursor(60,28);
display.print(outTempMin);
display.startscrollright(0x00, 0x00);
display.setCursor(0,0);
display.setTextSize(1);
display.drawLine(0, 9, 127, 9, 1);
String header = "Outdoor "+city;
display.println(header);
displayWiFi();
display.display();
}
void displayTime(int s)
{
unsigned long currentClockMillis = millis();
unsigned long epochTime =0;
int monthDay =0;
int currentMonth =0;
int currentYear =0;
String Date;
int hours;
int mins;
int seconds;
String Hours;
String Mins;
String Seconds;
if (s==0)
{
if (currentClockMillis - lastSecond >= 1000)
{
lastSecond = currentClockMillis;
Serial.print("Time: ");
Serial.println(timeClient.getFormattedTime());
//timeClient.update();
epochTime = timeClient.getEpochTime();
struct tm *ptm = gmtime ((time_t *)&epochTime);
PRINT("Day: ");
PRINT(daysOfTheWeek[timeClient.getDay()]);
PRINT(", ");
PRINT(timeClient.getHours());
PRINT(":");
PRINT(timeClient.getMinutes());
PRINT(":");
PRINTLN(timeClient.getSeconds());
monthDay = ptm->tm_mday;
PRINT("Month day: ");
PRINTLN(monthDay);
currentMonth = ptm->tm_mon+1;
PRINT("Month: ");
PRINT(currentMonth);
PRINTLN( months[currentMonth-1]);
currentYear = ptm->tm_year+1900;
Date = String(monthDay)+ "/" + String(currentMonth)+ "/"+String(currentYear);
hours = timeClient.getHours();
mins = timeClient.getMinutes();
seconds = timeClient.getSeconds();
Hours = String(hours);
Mins = String(mins);
Seconds = String(seconds);
display.stopscroll();
display.clearDisplay();
display.setTextSize(3);
display.setCursor(45,15);
//display.print(city);
display.setCursor(0,24);
if (hours<10) Hours = " "+Hours;
if (mins<10) Mins = "0"+Mins;
if (seconds<10) Seconds = "0"+Seconds;
display.fillRect(0, 24, SCREEN_WIDTH, 20, SSD1306_BLACK);
display.print(Hours+":"+Mins);
display.setTextSize(2);
display.setCursor(82, 31);
display.print("."+Seconds);
//display.startscrollright(0x00, 0x00);
display.setCursor(0,0);
display.setTextSize(1);
Date = String(daysOfTheWeek[timeClient.getDay()]) +" "+ Date;
display.setCursor((128-Date.length()*6)/2,0);
display.println(Date);
display.drawLine(0, 9, 127, 9, 1);
Serial.println(Date.length());
displayWiFi();
display.display();
}
}
}
void displayWiFi()
{
if(WiFi.status() == WL_CONNECTED)
display.drawBitmap(0, 48, epd_bitmap_wifi , 16, 16, WHITE);
}
void doubleClick() {
PRINTLN("DoubleClick() detected.");
showClock =!showClock;
}
int curchoice=1;
unsigned long lastmillis =0;
void selectCity(int ch)
{
display.stopscroll();
display.clearDisplay();
display.setCursor(25, 0);
display.setTextColor(WHITE);
display.println("Select City");
display.drawLine(0, 9, 127, 9, 1);
display.setCursor(8, 16);
int starty=16;
for(int i=1;i<6;i++){
if(ch == i) {
display.setCursor(8,starty);
display.setTextColor(BLACK, WHITE);
display.print(cities[i-1]); }
else {
display.setCursor(8,starty);
display.setTextColor(WHITE);
display.print(cities[i-1]);
}
starty+=9;
}
display.setTextColor(WHITE);
display.display();
}
int maxPics_L1 = 5;
int info = 0;
void singleClick()
{
PRINTLN("SingleClick() detected.");
lastmillis = millis();
//Serial.print("Pic: ");Serial.println(pic);
if(pic==0) {
if(info==0)
{
display.stopscroll();
display.clearDisplay();
display.print("WiFi IP: "); display.println(WiFi.localIP());
display.println("Uptime: " + String(lastmillis/1000)+ "s");
display.println("Min temp: " + outTempMin);
display.println("Max temp: " + outTempMax);
display.println("Feels like: "+ outFeels);
display.display();
}
if(info ==1)
{
displaySun();
}
delay(2000);
info++;
if(info>1) info = 0;
}
if(pic>=1 && pic<10)
{
if (pic >= maxPics_L1)
{
pic=1;
}
else if(pic < maxPics_L1)
{
pic++;
}
}
}
void longPressStart(){
lastmillis = millis();
if(pic==0) { pic=curchoice;return;}
if(pic>=1 && pic<=5)
{ getOutdoor();}
pic=0;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
PRINTLN("Hello, ESP!");
dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
getIndoor();
PRINTLN(inTemp);
PRINTLN(inHum);
PRINTLN("---");
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
PRINTLN(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
//display.setFont(&FreeSerif9pt7b);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,28);
display.println("Mini Weather Station");
display.drawBitmap(60, 40, epd_bitmap_wifi , 16, 16, WHITE);
display.display();
delay(2000);
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
PRINT(".");
}
PRINTLN(" Connected!");
//WiFi.setAutoReconnect(true);
// WiFi.persistent(true);
//attachInterrupt(digitalPinToInterrupt(buttonPin), checkTicks, CHANGE);
button.attachClick(singleClick);
button.attachDoubleClick(doubleClick);
button.attachLongPressStart(longPressStart);
timeClient.begin();
configTime(0, 0, "pool.ntp.org");
while (!time(nullptr)) {
delay(1000);
Serial.println("Waiting for time to synchronize...");
}
getOutdoor();
if (timeClient.update()) {
Serial.println("Synchronized with NTP.");
} else {
Serial.println("Error synchronizing with NTP.");
}
//WiFi.disconnect();
//Serial.println("Wi-Fi turned off.");
PRINTLN(Sun_rise);
PRINTLN(Sun_set);
display.display();
EEPROM.begin(12);
EEPROM.put(0, 13);
EEPROM.commit();
}
void loop() {
button.tick();
//delay(10);
//Serial.print("Start PIC: ");Serial.println(pic);
unsigned long currentMillis = millis();
if (millis() - lastSync > 30000) {
lastSync = millis();
if(WiFi.status() == WL_CONNECTED)
{
timeClient.begin();
if (timeClient.update()) {
Serial.println("Synchronized with NTP (in loop).");
} else {
Serial.println("Error synchronizing with NTP (in loop).");
}
}
}
if (millis() >= (lastmillis + maxtime))
{
pic = 0;
}
if(pic==0)
{
if ((millis() - lastTimeAcc) > OWMapDelay) {
// Send Events to the Web Server with the Sensor Readings
getOutdoor();
lastTimeAcc = millis();
}
if ((millis() - lastTime) > SensorDelay) {
// Send Events to the Web Server with the Sensor Readings
getIndoor();
lastTime = millis();
}
// Визначення, чи потрібно змінити стан
if (currentMillis - currentStateStartTime >= stateDuration) {
currentStateStartTime = currentMillis; // Оновлення часу початку поточного стану
// Скидаємо прапорці виведення для нового стану
printedState1 = false;
printedState2 = false;
printedState3 = false;
if(showClock) {
currentState = STATE_3;
}
// Зміна стану
switch (currentState) {
case STATE_1:
currentState = STATE_2;
break;
case STATE_2:
currentState = STATE_3;
break;
case STATE_3:
currentState = STATE_1;
break;
}
}
// Виконання дій залежно від поточного стану
switch (currentState) {
case STATE_1:
// Дії для "Стан 1"
if (!printedState1) {
PRINTLN("Виконуються дії для Стану 1");
displayTime(0);
printedState1 = false;
}
break;
case STATE_2:
// Дії для "Стан 2"
if (!printedState2) {
PRINTLN("Виконуються дії для Стану 2");
displayOutdoor();
printedState2 = true;
}
break;
case STATE_3:
// Дії для "Стан 3"
if (!printedState3) {
PRINTLN("Виконуються дії для Стану 3");
displayIndoor();
printedState3 = true;
}
break;
}
}
if(pic==1){
curchoice=1;
selectCity(1);
city = cities[0];
timeClient.setTimeOffset(7200);
}
if(pic==2){
curchoice=2;
selectCity(2);
city = cities[1];
timeClient.setTimeOffset(-25200);
}
if(pic==3){
curchoice=3;
selectCity(3);
city = cities[2];
timeClient.setTimeOffset(0);
}
if(pic==4){
curchoice=4;
selectCity(4);
city = cities[3];
timeClient.setTimeOffset(3600);
}
if(pic==5){
curchoice=5;
selectCity(5);
city = cities[4];
timeClient.setTimeOffset(3600);
}
// displayIndoor();
// delay(5000);
// displayOutdoor();
// delay(5000);
}