2024-11-12 16:10:34 +01:00

517 lines
24 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 <EEPROM.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;
int displayBrightness = 205;
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);
EEPROM.begin(512);
Wire.begin(); // Initialize I2C
Wire.setClock(400000L); // Set I2C to Fast Mode (400 kHz)
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(displayBrightness); // Value between 0 and 255
// 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 = 2700000; // 45 minutes in milliseconds
const unsigned long displayOverrideTimeout = 120000; // 2 minutes 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
displayBrightness -= 50;
if (displayBrightness < 10 ) {
displayBrightness = 255;
}
// Send the command to set the contrast
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(displayBrightness); // Value between 0 and 255
// 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
lastWeatherUpdate = millis() + weatherUpdateInterval + 1;
// 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;
}
}
float prevTemperature = 0.0;
int prevHumidity = 0;
float prevPressure = 0.0;
float prevWindSpeed = 0.0;
void saveWeatherData() {
// Store float values as bytes (4 bytes for each float)
EEPROM.put(0, prevTemperature);
EEPROM.put(4, prevHumidity);
EEPROM.put(8, prevPressure);
EEPROM.put(12, prevWindSpeed);
// Commit changes to EEPROM
EEPROM.commit(); // Write changes to flash
}
void loadWeatherData() {
// Read the data from EEPROM (addresses correspond to where they were saved)
EEPROM.get(0, prevTemperature);
EEPROM.get(4, prevHumidity);
EEPROM.get(8, prevPressure);
EEPROM.get(12, prevWindSpeed);
}
int icon = 0;
String weatherState;
float temperature;
int humidity;
float pressure;
float wind_speed;
int forecastIcons[3];
int currentYear, currentMonth, currentDay;
// Function to get a random message from an array
String getRandomMessage(const String messages[], int size) {
return messages[random(size)];
}
String generateTrendMessage(float current, float previous, const String& parameter) {
if (current > previous) {
return parameter + " rising ";
} else if (current < previous) {
return parameter + " falling ";
} else {
return parameter + " stable ";
}
}
bool fetchWeatherData() {
// Get current time
time_t epochTime = timeClient.getEpochTime();
struct tm *ptm = gmtime((time_t *)&epochTime);
currentDay = ptm->tm_mday;
currentMonth = ptm->tm_mon + 1;
currentYear = ptm->tm_year + 1900;
HTTPClient http;
// Request current weather
for (int attempt = 0; attempt < 3; attempt++) {
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);
if (!deserializeJson(doc, payload)) {
String mainWeather = doc["weather"][0]["main"].as<String>();
temperature = doc["main"]["temp"].as<float>() - 273.15;
humidity = doc["main"]["humidity"];
pressure = doc["main"]["pressure"].as<float>() / 1000;
wind_speed = doc["wind"]["speed"].as<float>();
if (mainWeather == "Clear") {
String clearMessages[] = {
"All systems nominal. Atmospheric clearance detected.",
"Clear skies confirmed. Solar energy levels stable for ship systems.",
"Sky conditions optimal for external scan. Proceed with visual sweep.",
"Visibility restored. All monitoring systems active and secure.",
"Open space ahead. Prepare for normal operations.",
"Solar reflection steady. Systems running at full capacity.",
"Clear. No interference detected. Navigation optimal.",
"Visible space clear. Proceed with standard operations.",
"Skies unclouded. No anomalies detected.",
"No obstructions in sight. Navigation systems operating normally.",
"Bright and clear skies. Optimal conditions for long-range communication.",
"Perfect visibility. All systems functioning within expected parameters.",
"Clear and stable. No impact on external activities.",
"The sky is free of disturbances. Proceed with confidence.",
"Atmospheric conditions steady. All sensors reporting normal."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(clearMessages, 15);
} else if (mainWeather == "Clouds") {
String cloudMessages[] = {
"Cloud cover detected. Light interference on visual systems.",
"Overcast conditions observed.",
"Shadows overhead. Adjusting sensitivity on light filters.",
"Moderate cloud formation detected. System performance normal.",
"Atmospheric interference present. Monitoring light levels.",
"Cloud coverage rising. Operational impact minimal.",
"Low-visibility overhead. Preparing for reduced solar input.",
"Sensors report cloud patterns. Adjusting visibility protocols.",
"Clouds forming in surrounding space. Adjusting navigational parameters.",
"Light cloud cover detected. No operational impact expected.",
"Cloud formation increasing. Visual scan in progress.",
"Partly cloudy. Visibility reduced, but no immediate danger.",
"Overcast sky. Sensors adjusting for changing light levels.",
"Increasing cloud density. Proceeding with caution on exterior tasks.",
"Cumulus clouds observed. No operational impact anticipated."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(cloudMessages, 15);
} else if (mainWeather == "Rain") {
String rainMessages[] = {
"Precipitation incoming. Shielding activated.",
"Rain detected. External moisture levels rising, activating water barriers.",
"Surface moisture increasing. Prepare for external wet conditions.",
"Rainstorm detected. External protection systems online.",
"Raindrops detected. Surface conditions becoming slick.",
"Incoming water influx. Prepare external surfaces for wet conditions.",
"Rain detected. Traction systems engaged for slippery terrain.",
"Wet conditions approaching. Hydration protocols for external units activated.",
"Raindrops increasing. Proceed with caution on exterior surfaces.",
"Heavy rain in the vicinity. External equipment may require adjustment.",
"Intense rain incoming. All systems on standby for moisture management.",
"Heavy precipitation. Surface traction adjustments being made.",
"Rainfall intensifying. Navigation systems recalibrating.",
"Storm-like rain detected. Prepare for potential delays in operations.",
"Rain approaching. All moisture-sensitive systems under review."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(rainMessages, 15);
} else if (mainWeather == "Drizzle") {
String drizzleMessages[] = {
"Light drizzle detected. Surface moisture rising, minimal impact.",
"Low-intensity rain detected. External systems functioning normally.",
"Fine mist detected. Prepare for minor surface wetting.",
"Gentle drizzle. Light hydration of external units detected.",
"Atmospheric moisture levels rising slowly. Proceed with minor caution.",
"Minor drizzle detected. No immediate impact on operations.",
"Drizzle detected. Adjusting exterior temperature controls.",
"Light rain confirmed. Surface conditions remain stable.",
"Drizzle present. External activity unaffected.",
"Micro-droplets detected. Surface wetting minimal.",
"Drizzle increasing. Minimal disruption to operational efficiency.",
"Light mist falling. Preparing exterior equipment for light moisture.",
"Slight drizzle detected. Monitoring for potential buildup.",
"Traces of rain observed. Surface conditioning proceeding normally.",
"Faint drizzle. No significant effect on operational systems."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(drizzleMessages, 15);
} else if (mainWeather == "Thunderstorm") {
String thunderstormMessages[] = {
"Warning: Severe storm approaching. High-voltage hazard detected.",
"Electrical storm detected in proximity. Shielding and surge protection engaged.",
"Thunderstorm alert: Prepare for sudden power fluctuations.",
"Storm in progress. Lightning detected. Surge protection active.",
"Electrical interference detected. Recalibrating external sensors.",
"Thunderstorm imminent. High-energy levels detected. Activating surge protocols.",
"Power surge imminent. Warning: high-voltage storm detected.",
"Electrical storm approaching. Brace for system disturbances.",
"Warning: Lightning detected. Secure sensitive systems immediately.",
"Severe atmospheric storm approaching. Power systems primed for protection.",
"Lightning detected. Power surge prevention systems activated.",
"Thunderstorm conditions intensifying. All external systems under review.",
"Severe electrical interference. Adjusting system tolerance for spikes.",
"Warning: Storm detected. Power fluctuations expected.",
"Thunderstorm alert. Lightning strike imminent. Systems on full defense."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(thunderstormMessages, 15);
} else if (mainWeather == "Snow") {
String snowMessages[] = {
"Cryogenic conditions detected. Snowfall in progress.",
"Snow accumulation imminent. External traction systems engaged.",
"Freezing precipitation confirmed. Temperature control systems adjusting.",
"Snow falling. Low-traction surfaces detected. Proceed with caution.",
"Heavy snowfall recorded. Thermal systems operating at full capacity.",
"Snowstorm imminent. Prepare for reduced external mobility.",
"Temperature drop confirmed. Snow accumulation expected.",
"Snow detected. De-icing systems online.",
"Cryogenic particles in atmosphere. Surface stability compromised.",
"Snowfall detected. All external movement restricted.",
"Snow level increasing. Traction systems are in full effect.",
"Blizzard conditions approaching. Prepare for limited visibility.",
"Heavy snow confirmed. Adjusting exterior systems for extreme conditions.",
"Low-temperature alert. Ice accumulation expected.",
"Snowstorm detected. Proceed with extreme caution on external surfaces."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(snowMessages, 15);
} else if (mainWeather == "Mist" || mainWeather == "Fog") {
String mistMessages[] = {
"Low-visibility conditions. Activate infrared scanning systems.",
"Fog detected. Navigation systems recalibrating for low-visibility operation.",
"Atmospheric opacity confirmed. Proceed with caution at reduced speed.",
"Dense mist detected. Visibility dropped to critical levels.",
"Fog levels increasing. Adjusting pathfinding parameters for accuracy.",
"Dense fog detected. Slowing navigation to safe speeds.",
"Increased fog density. Visual systems recalibrated.",
"Low visibility confirmed. Proceed with extreme caution.",
"Mist detected in sector. Visual enhancement systems engaged.",
"Fog detected. Reduced visibility affecting sensor accuracy.",
"Thick fog detected. External systems recalibrating for safety.",
"Reduced visibility confirmed. Navigating with extreme care.",
"Mist conditions detected. Heightened caution in exterior operations.",
"Fog rising in vicinity. Adjusting navigational parameters for safe course.",
"Low-visibility conditions. Monitoring environment closely."
};
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + getRandomMessage(mistMessages, 15);
} else {
weatherState = generateTrendMessage(temperature, prevTemperature, "Temperature") + generateTrendMessage(pressure, prevPressure, "Atmospheric pressure") + generateTrendMessage(humidity, prevHumidity, "Humidity") + generateTrendMessage(wind_speed, prevWindSpeed, "Wind speed") + "Unspecified weather anomaly detected. Monitoring closely.";
}
prevTemperature = temperature;
prevHumidity = humidity;
prevPressure = pressure;
prevWindSpeed = wind_speed;
saveWeatherData();
http.end();
break;
}
}
delay(1000); // Retry after 1 second
}
// Request forecast
for (int attempt = 0; attempt < 3; attempt++) {
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);
if (!deserializeJson(forecastDoc, payload)) {
for (int day = 0; day <= 2; day++) {
int state = forecastDoc["list"][day]["weather"][0]["id"];
if (state >= 200 && state <= 232) forecastIcons[day] = 6; // Thunderstorm
else if (state >= 300 && state <= 321) forecastIcons[day] = 5; // Drizzle
else if (state >= 500 && state <= 531) forecastIcons[day] = 3; // Rain
else if (state >= 600 && state <= 622) forecastIcons[day] = 4; // Snow
else if (state >= 701 && state <= 781) forecastIcons[day] = 2; // Mist
else if (state == 800) forecastIcons[day] = 0; // Clear
else if (state >= 801 && state <= 804) forecastIcons[day] = 1; // Clouds
else forecastIcons[day] = 0;
}
http.end();
return true;
}
}
delay(1000); // Retry after 1 second
}
return false; // Return false if data fetch fails
}
int scrollPos = SCREEN_WIDTH; // Global variable to keep track of the scroll position
unsigned long lastScrollTime = 0; // To control the scroll speed
const int scrollDelay = 80; // Delay in milliseconds between scroll updates
void displayWeatherData() {
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());
int stateWidth = strlen(weatherState.c_str()) * 12; // Approximate width of the status text in pixels
// Clear the area for the file name to avoid overlap
display.fillRect(0, 21, SCREEN_WIDTH, 11, BLACK);
// Scroll the text to the left
if (!is_display_off) {
if (millis() - lastScrollTime > scrollDelay) {
// Update scroll position
scrollPos -= 5; // Move left by 1 pixel each time
lastScrollTime = millis();
// If the text has completely scrolled off, reset scroll position to start from the right
if (scrollPos < -stateWidth) {
scrollPos = SCREEN_WIDTH;
}
}
}
// Draw the file name with current scroll position
display.setTextSize(2);
display.setCursor(scrollPos, 21);
display.print(weatherState);
// Clear the area for "Time left" display before printing
display.fillRect(0, 37, SCREEN_WIDTH, 27, BLACK); // Clear area for "Time left"
display.setCursor(0, 44);
display.setTextSize(1);
display.printf(" %5.2f C %d%%\r\n", temperature, humidity);
display.drawRect(43, 44, 3, 3, WHITE); // Degree symbol
display.setCursor(0, 55);
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.drawLine(0, 40, 127, 40, 1);
display.drawBitmap(0, 42, temperature_icon, 10, 10, WHITE);
display.drawBitmap(74, 42, humidity_icon, 10, 10, WHITE);
display.drawBitmap(0, 54, pressure_icon, 10, 10, WHITE);
display.drawBitmap(74, 54, wind_icon, 10, 10, WHITE);
int pos = 69;
for (int i = 0; i < 3; i++) {
display.drawBitmap(pos, 0, bitmap_icons[forecastIcons[i]], 16, 16, WHITE);
pos += 20;
}
display.display();
}
void setup(void) {
initSystems();
netman.start();
timeClient.begin();
timeClient.update();
loadWeatherData();
fetchWeatherData();
}
void loop() {
timeClient.update();
commonButtonHandler();
displayWeatherData();
if (display_override && (millis() - lastDisplayOverride > displayOverrideTimeout)){
display_override = false;
lastDisplayOverride = millis();
}
if (!is_display_off && (millis() - lastWeatherUpdate > weatherUpdateInterval)) {
fetchWeatherData();
lastWeatherUpdate = millis();
scrollPos = SCREEN_WIDTH;
}
powerSaveCheck();
}