336 lines
11 KiB
C++
336 lines
11 KiB
C++
#include "config.h"
|
|
#include <ESP8266WiFi.h>
|
|
#include <ESP8266HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
#include <Wire.h>
|
|
#include <NTPClient.h>
|
|
#include <WiFiUdp.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include "bitmaps.h"
|
|
#include "netman.h"
|
|
|
|
// Init display and wifi
|
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
netman netman(display);
|
|
WiFiClient client;
|
|
|
|
// Define NTP Client to get time
|
|
WiFiUDP ntpUDP;
|
|
NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 43200000);
|
|
|
|
// Define these in config.h
|
|
const String Location = LOCATION;
|
|
const String API_Key = API_KEY;
|
|
|
|
void capitalizeFirstLetter(char* str) {
|
|
if (str != nullptr && *str != '\0') {
|
|
str[0] = toupper(str[0]);
|
|
}
|
|
}
|
|
|
|
void beep(int buzz) {
|
|
tone(PIN_BUZZER, buzz, 100);
|
|
delay(100);
|
|
noTone(PIN_BUZZER);
|
|
}
|
|
|
|
void initSystems() {
|
|
pinMode(PIN_BTN_L, INPUT);
|
|
pinMode(PIN_BTN_M, INPUT);
|
|
pinMode(PIN_BTN_R, INPUT);
|
|
pinMode(PIN_BUZZER, OUTPUT);
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
// Initialize the SSD1306 display
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Adjust I2C address if needed
|
|
for(;;); // Don't proceed, loop forever
|
|
}
|
|
|
|
// Rotate the display 180 degrees
|
|
display.setRotation(2);
|
|
|
|
// Clear the display buffer
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setTextColor(WHITE);
|
|
display.setCursor(0, 0);
|
|
display.display();
|
|
}
|
|
|
|
// Timers for dynamic updates
|
|
unsigned long lastWeatherUpdate = 0;
|
|
unsigned long lastDisplayOverride = 0;
|
|
const unsigned long weatherUpdateInterval = 1800000; // 30 minutes in milliseconds
|
|
const unsigned long displayOverrideTimeout = 60000; // 1 minute in milliseconds
|
|
bool is_display_off = false;
|
|
bool display_override = false;
|
|
|
|
void commonButtonHandler() {
|
|
unsigned long currentMillis = millis();
|
|
static unsigned long leftPressStart = 0;
|
|
static unsigned long middlePressStart = 0;
|
|
static unsigned long rightPressStart = 0;
|
|
|
|
static bool leftBeeped = false;
|
|
static bool middleBeeped = false;
|
|
static bool rightBeeped = false;
|
|
|
|
bool leftPressed = (digitalRead(PIN_BTN_L) == HIGH);
|
|
bool middlePressed = (digitalRead(PIN_BTN_M) == HIGH);
|
|
bool rightPressed = (digitalRead(PIN_BTN_R) == HIGH);
|
|
|
|
// Short press detection
|
|
if (leftPressed) {
|
|
if ((currentMillis - leftPressStart > 50)) { // Debounce delay
|
|
if (!leftBeeped) {
|
|
beep(1000); // Play beep sound
|
|
leftBeeped = true; // Set beeped state
|
|
// Handle left short press action here
|
|
}
|
|
}
|
|
} else {
|
|
leftPressStart = currentMillis; // Reset the timer if button is not pressed
|
|
leftBeeped = false; // Reset beeped state
|
|
}
|
|
|
|
if (middlePressed) {
|
|
if ((currentMillis - middlePressStart > 50)) { // Debounce delay
|
|
if (!middleBeeped) {
|
|
beep(1000); // Play beep sound
|
|
middleBeeped = true; // Set beeped state
|
|
// Handle middle short press action here
|
|
}
|
|
}
|
|
} else {
|
|
middlePressStart = currentMillis; // Reset the timer if button is not pressed
|
|
middleBeeped = false; // Reset beeped state
|
|
}
|
|
|
|
if (rightPressed) {
|
|
if ((currentMillis - rightPressStart > 50)) { // Debounce delay
|
|
if (!rightBeeped) {
|
|
beep(1000); // Play beep sound
|
|
rightBeeped = true; // Set beeped state
|
|
if (is_display_off) {
|
|
display.ssd1306_command(SSD1306_DISPLAYON); // Turn on display
|
|
is_display_off = false;
|
|
display_override = true;
|
|
beep(1300);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
rightPressStart = currentMillis; // Reset the timer if button is not pressed
|
|
rightBeeped = false; // Reset beeped state
|
|
}
|
|
|
|
// Long press detection
|
|
if (leftPressed && (currentMillis - leftPressStart > 2000)) {
|
|
if (!leftBeeped) {
|
|
beep(1000); // Play beep sound
|
|
leftBeeped = true; // Set beeped state
|
|
// Handle left long press action here
|
|
}
|
|
}
|
|
|
|
if (middlePressed && (currentMillis - middlePressStart > 2000)) {
|
|
if (!middleBeeped) {
|
|
beep(1000); // Play beep sound
|
|
middleBeeped = true; // Set beeped state
|
|
// Handle middle long press action here
|
|
}
|
|
}
|
|
|
|
if (rightPressed && (currentMillis - rightPressStart > 2000)) {
|
|
if (!is_display_off) {
|
|
beep(1300);
|
|
display.ssd1306_command(SSD1306_DISPLAYOFF); // Turn off display
|
|
is_display_off = true;
|
|
display_override = false;
|
|
beep(1000);
|
|
}
|
|
}
|
|
|
|
// Combination of Left and Middle long press
|
|
if (leftPressed && middlePressed &&
|
|
(currentMillis - leftPressStart > 2000) && (currentMillis - middlePressStart > 2000)) {
|
|
ESP.restart();
|
|
}
|
|
}
|
|
|
|
void powerSaveCheck() {
|
|
int currentHour = timeClient.getHours();
|
|
if ((currentHour >= 22 || currentHour < 8) && !is_display_off && !display_override) {
|
|
display.ssd1306_command(SSD1306_DISPLAYOFF); // Turn off display for power-saving
|
|
is_display_off = true;
|
|
}
|
|
}
|
|
|
|
int icon = 0;
|
|
bool fetchWeatherData() {
|
|
// Get current time
|
|
time_t epochTime = timeClient.getEpochTime();
|
|
struct tm *ptm = gmtime((time_t *)&epochTime);
|
|
int currentDay = ptm->tm_mday;
|
|
int currentMonth = ptm->tm_mon + 1;
|
|
int currentYear = ptm->tm_year + 1900;
|
|
HTTPClient http;
|
|
|
|
int attempt;
|
|
bool dataFetched = false;
|
|
|
|
// Request current weather
|
|
for (attempt = 0; attempt < 3; attempt++) {
|
|
// Request current weather
|
|
http.begin(client, "http://api.openweathermap.org/data/2.5/weather?q=" + Location + "&APPID=" + API_Key);
|
|
int httpCode = http.GET();
|
|
if (httpCode > 0) {
|
|
String payload = http.getString();
|
|
DynamicJsonDocument doc(1024);
|
|
|
|
DeserializationError error = deserializeJson(doc, payload);
|
|
if (!error) {
|
|
const char* state = doc["weather"][0]["description"];
|
|
capitalizeFirstLetter(const_cast<char*>(state));
|
|
|
|
float temp = doc["main"]["temp"].as<float>() - 273.15;
|
|
int humidity = doc["main"]["humidity"];
|
|
float pressure = doc["main"]["pressure"].as<float>() / 1000;
|
|
float wind_speed = doc["wind"]["speed"].as<float>();
|
|
|
|
int statusSize = strlen(state) > 12 ? 1 : 2;
|
|
|
|
// Display data
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0, 0);
|
|
display.printf("%d-%d-%d\r\n", currentYear, currentMonth, currentDay);
|
|
display.setCursor(0, 9);
|
|
display.printf("%s", Location.c_str());
|
|
display.setTextSize(statusSize);
|
|
display.setCursor(0, 21);
|
|
display.printf("%s\r\n", state);
|
|
display.setCursor(0, 39);
|
|
display.setTextSize(1);
|
|
display.printf(" %5.2f C %d%%\r\n", temp, humidity);
|
|
display.drawRect(43, 39, 3, 3, WHITE); // Degree symbol
|
|
display.setCursor(0, 52);
|
|
display.printf(" %.3fbar %.1fm/s \r\n", pressure, wind_speed);
|
|
display.drawLine(0, 18, 127, 18, 1);
|
|
display.drawLine(65, 18, 65, 0, 1);
|
|
display.drawBitmap(0, 38, temperature_icon, 10, 10, WHITE);
|
|
display.drawBitmap(74, 38, humidity_icon, 10, 10, WHITE);
|
|
display.drawBitmap(0, 51, pressure_icon, 10, 10, WHITE);
|
|
display.drawBitmap(74, 51, wind_icon, 10, 10, WHITE);
|
|
display.display();
|
|
|
|
http.end();
|
|
dataFetched = true;
|
|
break; // Exit the loop after successful fetch
|
|
}
|
|
}
|
|
// Display attempt number
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0, 0);
|
|
display.printf("Attempt %d/3", attempt + 1);
|
|
display.display();
|
|
delay(1000); // Retry after 1 second if the request fails
|
|
}
|
|
|
|
if (!dataFetched) {
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0, 0);
|
|
display.printf("Failed to fetch weather");
|
|
display.display();
|
|
http.end();
|
|
return false; // Return false if the fetch fails after 3 attempts
|
|
}
|
|
|
|
// Request forecast
|
|
for (attempt = 0; attempt < 3; attempt++) {
|
|
// Request forecast
|
|
http.begin(client, "http://api.openweathermap.org/data/2.5/forecast?q=" + Location + "&APPID=" + API_Key + "&cnt=3");
|
|
int httpCode = http.GET();
|
|
if (httpCode > 0) {
|
|
String payload = http.getString();
|
|
DynamicJsonDocument forecastDoc(1024);
|
|
|
|
DeserializationError error = deserializeJson(forecastDoc, payload);
|
|
if (!error) {
|
|
int pos = 69;
|
|
for (int day = 0; day <= 2; day++) {
|
|
int state = forecastDoc["list"][day]["weather"][0]["id"];
|
|
|
|
// Determine icon based on state ID
|
|
if (state >= 200 && state <= 232) {
|
|
icon = 6; // Thunderstorm
|
|
} else if (state >= 300 && state <= 321) {
|
|
icon = 5; // Drizzle
|
|
} else if (state >= 500 && state <= 531) {
|
|
icon = 3; // Rain
|
|
} else if (state >= 600 && state <= 622) {
|
|
icon = 4; // Snow
|
|
} else if (state >= 701 && state <= 781) {
|
|
icon = 2; // Mist
|
|
} else if (state == 800) {
|
|
icon = 0; // Clear
|
|
} else if (state >= 801 && state <= 804) {
|
|
icon = 1; // Clouds
|
|
} else {
|
|
icon = 0; // Default icon
|
|
}
|
|
|
|
display.drawBitmap(pos, 0, bitmap_icons[icon], 16, 16, WHITE);
|
|
pos += 20;
|
|
display.display();
|
|
}
|
|
http.end();
|
|
break; // Exit the loop after successful fetch
|
|
}
|
|
}
|
|
// Display attempt number for forecast
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0, 0);
|
|
display.printf("Forecast attempt %d/3", attempt + 1);
|
|
display.display();
|
|
delay(1000); // Retry after 1 second if the request fails
|
|
}
|
|
|
|
if (!dataFetched) {
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0, 0);
|
|
display.printf("Failed to fetch forecast");
|
|
display.display();
|
|
return false; // Return false if the forecast fetch fails after 3 attempts
|
|
}
|
|
|
|
return true; // Return true if both data fetches were successful
|
|
}
|
|
|
|
void setup(void) {
|
|
initSystems();
|
|
netman.start();
|
|
timeClient.begin();
|
|
timeClient.update();
|
|
fetchWeatherData();
|
|
}
|
|
|
|
void loop() {
|
|
timeClient.update();
|
|
if (display_override && (millis() - lastDisplayOverride > displayOverrideTimeout)){
|
|
display_override = false;
|
|
lastDisplayOverride = millis();
|
|
}
|
|
if (!is_display_off && (millis() - lastWeatherUpdate > weatherUpdateInterval)) {
|
|
fetchWeatherData();
|
|
lastWeatherUpdate = millis();
|
|
}
|
|
powerSaveCheck();
|
|
commonButtonHandler();
|
|
}
|