diff --git a/README.md b/README.md index 035081c6c..7da63593c 100644 --- a/README.md +++ b/README.md @@ -23,21 +23,19 @@ Here are two examples utilizing various configuration options:

- ## Contents -- [Setup Guide](#setup-guide) - - [Hardware](#hardware) - - [Wiring](#wiring) - - [Configuration, Compilation, and Upload](#configuration-compilation-and-upload) - - [OpenWeatherMap API Key](#openweathermap-api-key) -- [Error Messages and Troubleshooting](#error-messages-and-troubleshooting) - - [Low Battery](#low-battery) - - [WiFi Connection](#wifi-connection) - - [API Error](#api-error) - - [Time Server Error](#time-server-error) -- [Licensing](#licensing) - +- [Setup Guide](#setup-guide) + - [Hardware](#hardware) + - [Wiring](#wiring) + - [Configuration, Compilation, and Upload](#configuration-compilation-and-upload) + - [OpenWeatherMap API Key](#openweathermap-api-key) +- [Error Messages and Troubleshooting](#error-messages-and-troubleshooting) + - [Low Battery](#low-battery) + - [WiFi Connection](#wifi-connection) + - [API Error](#api-error) + - [Time Server Error](#time-server-error) +- [Licensing](#licensing) ## Setup Guide @@ -46,9 +44,11 @@ Here are two examples utilizing various configuration options: 7.5inch (800×480) E-Paper Display - Advantages of E-Paper + - Ultra Low Power Consumption - E-Paper (aka E-Ink) displays are ideal for low-power applications that do not require frequent display refreshes. E-Paper displays only draw power when refreshing the display and do not have a backlight. Images will remain on the screen even when power is removed. - Limitations of E-Paper: + - Colors - E-Paper has traditionally been limited to just black and white, but in recent years 3-color E-Paper screens have started showing up. - Refresh Times and Ghosting - E-Paper displays are highly susceptible to ghosting effects if refreshed too quickly. To avoid this, E-Paper displays often take a few seconds to refresh(4s for the unit used in this project) and will alternate between black and white a few times, which can be distracting. @@ -57,16 +57,16 @@ Here are two examples utilizing various configuration options: Waveshare and Good Display make equivalent panels. Either variant will work. - | Panel | Resolution | Colors | Notes | - |-----------------------------------------|------------|-----------------|-----------------------------------------------------------------------------------------------------------------------| - | Waveshare 7.5in e-paper (v2) | 800x480px | Black/White | Available [here](https://www.waveshare.com/product/7.5inch-e-paper.htm). (recommended) | - | Good Display 7.5in e-paper (GDEY075T7) | 800x480px | Black/White | Available [here](https://www.aliexpress.com/item/3256802683908868.html). (recommended) | - | Waveshare 7.5in e-Paper (B) | 800x480px | Red/Black/White | Available [here](https://www.waveshare.com/product/7.5inch-e-paper-b.htm). | - | Good Display 7.5in e-paper (GDEY075Z08) | 800x480px | Red/Black/White | Available [here](https://www.aliexpress.com/item/3256803540460035.html). | - | Waveshare 7.3in ACeP e-Paper (F) | 800x480px | 7-Color | Available [here](https://www.waveshare.com/product/displays/e-paper/epaper-1/7.3inch-e-paper-f.htm). | - | Good Display 7.3in e-paper (GDEY073D46) | 800x480px | 7-Color | Available [here](https://www.aliexpress.com/item/3256805485098421.html). | - | Waveshare 7.5in e-paper (v1) | 640x384px | Black/White | Limited support. Some information not displayed, see [image](showcase/demo-waveshare75-version1.jpg). | - | Good Display 7.5in e-paper (GDEW075T8) | 640x384px | Black/White | Limited support. Some information not displayed, see [image](showcase/demo-waveshare75-version1.jpg). | + | Panel | Resolution | Colors | Notes | + | --------------------------------------- | ---------- | --------------- | ----------------------------------------------------------------------------------------------------- | + | Waveshare 7.5in e-paper (v2) | 800x480px | Black/White | Available [here](https://www.waveshare.com/product/7.5inch-e-paper.htm). (recommended) | + | Good Display 7.5in e-paper (GDEY075T7) | 800x480px | Black/White | Available [here](https://www.aliexpress.com/item/3256802683908868.html). (recommended) | + | Waveshare 7.5in e-Paper (B) | 800x480px | Red/Black/White | Available [here](https://www.waveshare.com/product/7.5inch-e-paper-b.htm). | + | Good Display 7.5in e-paper (GDEY075Z08) | 800x480px | Red/Black/White | Available [here](https://www.aliexpress.com/item/3256803540460035.html). | + | Waveshare 7.3in ACeP e-Paper (F) | 800x480px | 7-Color | Available [here](https://www.waveshare.com/product/displays/e-paper/epaper-1/7.3inch-e-paper-f.htm). | + | Good Display 7.3in e-paper (GDEY073D46) | 800x480px | 7-Color | Available [here](https://www.aliexpress.com/item/3256805485098421.html). | + | Waveshare 7.5in e-paper (v1) | 640x384px | Black/White | Limited support. Some information not displayed, see [image](showcase/demo-waveshare75-version1.jpg). | + | Good Display 7.5in e-paper (GDEW075T8) | 640x384px | Black/White | Limited support. Some information not displayed, see [image](showcase/demo-waveshare75-version1.jpg). | This software has limited support for accent colors. E-paper panels with additional colors tend to have longer refresh times, which will reduce battery life. @@ -77,10 +77,8 @@ DESPI-C02 Adapter Board - The Waveshare HATs (rev 2.2/2.3) are not recommended. Their compatibility with this project is not regularly tested. - https://www.e-paper-display.com/products_detail/productId=403.html - - https://www.aliexpress.us/item/3256804446769469.html - FireBeetle 2 ESP32-E Microcontroller - Why the ESP32? @@ -101,30 +99,25 @@ FireBeetle 2 ESP32-E Microcontroller - FireBeetle ESP32 models include onboard circuitry to monitor battery voltage of a battery connected to its JST-PH2.0 connector. - - - BME280 - Pressure, Temperature, and Humidity Sensor - - Provides accurate indoor temperature and humidity. - Much faster than the DHT22, which requires a 2-second wait before reading temperature and humidity samples. - -3.7V Lipo Battery w/ 2 Pin JST Connector - + 3.7V Lipo Battery w/ 2 Pin JST Connector - Size is up to you. I used a 5000mah battery so that the device can operate on a single charge for >6 months. - - The battery can be charged by plugging the FireBeetle ESP32 into the wall via the USB-C connector while the battery is plugged into the ESP32's JST connector. > **Warning** > The polarity of JST-PH2.0 connectors is not standardized! You may need to swap the order of the wires in the connector. Stand/Frame + - You'll want a nice way to show off your project. Here are a few popular choices. - DIY Wooden - I made a small stand by hollowing out a piece of wood from the bottom. On the back, I used a short USB extension cable so that I can charge the battery without needing to remove the components from the stand. I also wired a small reset button to refresh the display manually. Additionally, I 3d printed a cover for the bottom, which is held on by magnets. The E-paper screen is very thin, so I used a thin piece of acrylic to support it. @@ -135,10 +128,11 @@ Stand/Frame screen angle = 80deg
screen is 15mm from the front - 3D Printable + - Here is a list of community designs. - + | Contributor | Link | - |----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| + | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | [Kingfisher](https://www.printables.com/@Kingfisher_32821) | [Printables](https://www.printables.com/model/1139047-weather-station-e-ink-frame) | | [Francois Allard](https://www.printables.com/@FrAllard_1585397) | [Printables](https://www.printables.com/model/791477-weather-station-using-a-esp32) | | [3D Nate](https://www.printables.com/@3DNate_451157) | [Printables](https://www.printables.com/model/661183-e-ink-weather-station-frame) | @@ -149,8 +143,8 @@ Stand/Frame | [MPHarms](https://www.thingiverse.com/mpharms/designs) | [Thingiverse](https://www.thingiverse.com/thing:6666148) | - If you want to share your own 3D printable designs, your contributions are highly encouraged and welcome! -- Picture Frame +- Picture Frame ### Wiring @@ -184,7 +178,6 @@ Cut the low power pad for even longer battery life.

- ### Configuration, Compilation, and Upload PlatformIO for VSCode is used for managing dependencies, code compilation, and uploading to ESP32. @@ -203,9 +196,11 @@ PlatformIO for VSCode is used for managing dependencies, code compilation, and u 5. Configure Options. - - Most configuration options are located in [config.cpp](platformio/src/config.cpp), with a few in [config.h](platformio/include/config.h). Locale/language options can also be found in include/locales/locale_*.inc. + - Most configuration options are located in [config.cpp](platformio/src/config.cpp), with a few in [config.h](platformio/include/config.h) and [credentials.h](platformio/include/credentials.h). Locale/language options can also be found in include/locales/locale\_\*.inc. - - Important settings to configure in config.cpp: + - First copy `platformio/include/credentials.h.template` to `platformio/include/credentials.h` + + - Important settings to configure in credentials.h: - WiFi credentials (ssid, password). @@ -213,6 +208,8 @@ PlatformIO for VSCode is used for managing dependencies, code compilation, and u - Latitude and longitude. + - Important settings to configure in config.cpp: + - Time and date formats. - Sleep duration. @@ -231,11 +228,11 @@ PlatformIO for VSCode is used for managing dependencies, code compilation, and u b. Click the upload arrow along the bottom of the VSCode window. (Should say "PlatformIO: Upload" if you hover over it.) - - PlatformIO will automatically download the required third-party libraries, compile, and upload the code. :) + - PlatformIO will automatically download the required third-party libraries, compile, and upload the code. :) - - You will only see this if you have the PlatformIO extension installed. + - You will only see this if you have the PlatformIO extension installed. - - If you are getting errors during the upload process, you may need to install drivers to allow you to upload code to the ESP32. + - If you are getting errors during the upload process, you may need to install drivers to allow you to upload code to the ESP32. ### OpenWeatherMap API Key @@ -246,25 +243,29 @@ This project will make calls to 2 different APIs ("One Call" and "Air Pollution" - The One Call API 3.0 is only included in the "One Call by Call" subscription. This separate subscription includes 1,000 calls/day for free and allows you to pay only for the number of API calls made to this product. Here's how to subscribe and avoid any credit card changes: - - Go to - - Follow the instructions to complete the subscription. - - Go to and set the "Calls per day (no more than)" to 1,000. This ensures you will never overrun the free calls. + +- Go to +- Follow the instructions to complete the subscription. +- Go to and set the "Calls per day (no more than)" to 1,000. This ensures you will never overrun the free calls. ## Error Messages and Troubleshooting ### Low Battery + This error screen appears once the battery voltage has fallen below LOW_BATTERY_VOLTAGE (default = 3.20v). The display will not refresh again until it detects battery voltage above LOW_BATTERY_VOLTAGE. When battery voltage is between LOW_BATTERY_VOLTAGE and VERY_LOW_BATTERY_VOLTAGE (default = 3.10v) the esp32 will deep-sleep for periods of LOW_BATTERY_SLEEP_INTERVAL (default = 30min) before checking battery voltage again. If the battery voltage falls between LOW_BATTERY_SLEEP_INTERVAL and CRIT_LOW_BATTERY_VOLTAGE (default = 3.00v), then the display will deep-sleep for periods VERY_LOW_BATTERY_SLEEP_INTERVAL (default = 120min). If battery voltage falls below CRIT_LOW_BATTERY_VOLTAGE, then the esp32 will enter hibernate mode and will require a manual push of the reset (RST) button to begin updating again.
### WiFi Connection + This error screen appears when the ESP32 fails to connect to WiFi. If the message reads "WiFi Connection Failed" this might indicate an incorrect password. If the message reads "SSID Not Available" this might indicate that you mistyped the SSID or that the esp32 is out of the range of the access point. The esp32 will retry once every SLEEP_DURATION (default = 30min).
### API Error + This error screen appears if an error (client or server) occurs when making an API request to OpenWeatherMap. The second line will give the error code followed by a descriptor phrase. Positive error codes correspond to HTTP response status codes, while error codes <= 0 indicate a client(esp32) error. The esp32 will retry once every SLEEP_DURATION (default = 30min).

@@ -273,6 +274,7 @@ In the example shown to the left, "401: Unauthorized" may be the result of an in
### Time Server Error + This error screen appears when the esp32 fails to fetch the time from NTP_SERVER_1/NTP_SERVER_2. This error sometimes occurs immediately after uploading to the esp32; in this case, just hit the reset button or wait for SLEEP_DURATION (default = 30min) and the esp32 to automatically retry. If the error persists, try selecting closer/lower latency time servers or increasing NTP_TIMEOUT. @@ -282,27 +284,26 @@ This error screen appears when the esp32 fails to fetch the time from NTP_SERVER esp32-weather-epd is licensed under the [GNU General Public License v3.0](LICENSE) with tools, fonts, and icons whose licenses are as follows: -| Name | License | Description | -|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| -| [Adafruit-GFX-Library: fontconvert](https://github.com/adafruit/Adafruit-GFX-Library/tree/master/fontconvert) | [BSD License](fonts/fontconvert/license.txt) | CLI tool for preprocessing fonts to be used with the Adafruit_GFX Arduino library. | -| [pollutant-concentration-to-aqi](https://github.com/lmarzen/pollutant-concentration-to-aqi) | [GNU Lesser General Public License v2.1](platformio/lib/pollutant-concentration-to-aqi/LICENSE) | C library that converts pollutant concentrations to Air Quality Index(AQI). | -| [GNU FreeFont](https://www.gnu.org/software/freefont/) | [GNU General Public License v3.0](https://www.gnu.org/software/freefont/license.html) | Font Family | -| [Lato](https://fonts.google.com/specimen/Lato) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Montserrat](https://fonts.google.com/specimen/Montserrat) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Open Sans](https://fonts.google.com/specimen/Open+Sans) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Poppins](https://fonts.google.com/specimen/Poppins) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Quicksand](https://fonts.google.com/specimen/Quicksand) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Raleway](https://fonts.google.com/specimen/Raleway) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | -| [Roboto](https://fonts.google.com/specimen/Roboto) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | -| [Roboto Mono](https://fonts.google.com/specimen/Roboto+Mono) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | -| [Roboto Slab](https://fonts.google.com/specimen/Roboto+Slab) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | -| [Ubuntu font](https://design.ubuntu.com/font) | [Ubuntu Font Licence v1.0](https://ubuntu.com/legal/font-licence) | Font Family | -| [Weather Themed Icons](https://github.com/erikflowers/weather-icons) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | (wi-**.svg) Weather icon family by Lukas Bischoff/Erik Flowers. | -| [Google Icons](https://fonts.google.com/icons) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | (battery**.svg, visibility_icon.svg) Battery and visibility icons from Google Icons. | -| [Biological Hazard Symbol](https://svgsilh.com/image/37775.html) | [CC0 v1.0](https://en.wikipedia.org/wiki/Public_domain) | (biological_hazard_symbol.svg) Biohazard icon. | -| [House Icon](https://seekicon.com/free-icon/house_16) | [MIT License](http://opensource.org/licenses/mit-license.html) | (house.svg) House icon. | -| [Indoor Temerature/Humidity Icons](icons/svg) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | (house_**.svg) Indoor temerature/humidity icons. | -| [Ionizing Radiation Symbol](https://svgsilh.com/image/309911.html) | [CC0 v1.0](https://creativecommons.org/publicdomain/zero/1.0/) | (ionizing_radiation_symbol.svg) Ionizing radiation icons. | -| [Phosphor Icons](https://github.com/phosphor-icons/homepage) | [MIT License](http://opensource.org/licenses/mit-license.html) | (wifi**.svg, warning_icon.svg, error_icon.svg) WiFi, Warning, and Error icons from Phosphor Icons. | -| [Wind Direction Icon](https://www.onlinewebfonts.com/icon/251550) | [CC BY v3.0](http://creativecommons.org/licenses/by/3.0) | (meteorological_wind_direction_**deg.svg) Meteorological wind direction icon from Online Web Fonts. | - +| Name | License | Description | +| ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| [Adafruit-GFX-Library: fontconvert](https://github.com/adafruit/Adafruit-GFX-Library/tree/master/fontconvert) | [BSD License](fonts/fontconvert/license.txt) | CLI tool for preprocessing fonts to be used with the Adafruit_GFX Arduino library. | +| [pollutant-concentration-to-aqi](https://github.com/lmarzen/pollutant-concentration-to-aqi) | [GNU Lesser General Public License v2.1](platformio/lib/pollutant-concentration-to-aqi/LICENSE) | C library that converts pollutant concentrations to Air Quality Index(AQI). | +| [GNU FreeFont](https://www.gnu.org/software/freefont/) | [GNU General Public License v3.0](https://www.gnu.org/software/freefont/license.html) | Font Family | +| [Lato](https://fonts.google.com/specimen/Lato) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Montserrat](https://fonts.google.com/specimen/Montserrat) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Open Sans](https://fonts.google.com/specimen/Open+Sans) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Poppins](https://fonts.google.com/specimen/Poppins) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Quicksand](https://fonts.google.com/specimen/Quicksand) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Raleway](https://fonts.google.com/specimen/Raleway) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | Font Family | +| [Roboto](https://fonts.google.com/specimen/Roboto) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | +| [Roboto Mono](https://fonts.google.com/specimen/Roboto+Mono) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | +| [Roboto Slab](https://fonts.google.com/specimen/Roboto+Slab) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | Font Family | +| [Ubuntu font](https://design.ubuntu.com/font) | [Ubuntu Font Licence v1.0](https://ubuntu.com/legal/font-licence) | Font Family | +| [Weather Themed Icons](https://github.com/erikflowers/weather-icons) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | (wi-\*\*.svg) Weather icon family by Lukas Bischoff/Erik Flowers. | +| [Google Icons](https://fonts.google.com/icons) | [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0) | (battery\*\*.svg, visibility_icon.svg) Battery and visibility icons from Google Icons. | +| [Biological Hazard Symbol](https://svgsilh.com/image/37775.html) | [CC0 v1.0](https://en.wikipedia.org/wiki/Public_domain) | (biological_hazard_symbol.svg) Biohazard icon. | +| [House Icon](https://seekicon.com/free-icon/house_16) | [MIT License](http://opensource.org/licenses/mit-license.html) | (house.svg) House icon. | +| [Indoor Temerature/Humidity Icons](icons/svg) | [SIL OFL v1.1](http://scripts.sil.org/OFL) | (house\_\*\*.svg) Indoor temerature/humidity icons. | +| [Ionizing Radiation Symbol](https://svgsilh.com/image/309911.html) | [CC0 v1.0](https://creativecommons.org/publicdomain/zero/1.0/) | (ionizing_radiation_symbol.svg) Ionizing radiation icons. | +| [Phosphor Icons](https://github.com/phosphor-icons/homepage) | [MIT License](http://opensource.org/licenses/mit-license.html) | (wifi\*\*.svg, warning_icon.svg, error_icon.svg) WiFi, Warning, and Error icons from Phosphor Icons. | +| [Wind Direction Icon](https://www.onlinewebfonts.com/icon/251550) | [CC BY v3.0](http://creativecommons.org/licenses/by/3.0) | (meteorological*wind_direction*\*\*deg.svg) Meteorological wind direction icon from Online Web Fonts. | diff --git a/platformio/.gitignore b/platformio/.gitignore index b9f3806a2..bbba29c37 100644 --- a/platformio/.gitignore +++ b/platformio/.gitignore @@ -1,2 +1,3 @@ .pio .vscode +credentials.h diff --git a/platformio/include/api_response.h b/platformio/include/api_response.h index 8391dec2f..5da1e69e4 100644 --- a/platformio/include/api_response.h +++ b/platformio/include/api_response.h @@ -25,18 +25,18 @@ #include #include -#define OWM_NUM_MINUTELY 1 // 61 -#define OWM_NUM_HOURLY 48 // 48 -#define OWM_NUM_DAILY 8 // 8 -#define OWM_NUM_ALERTS 8 // OpenWeatherMaps does not specify a limit, but if you need more alerts you are probably doomed. +#define OWM_NUM_MINUTELY 1 // 61 +#define OWM_NUM_HOURLY 48 // 48 +#define OWM_NUM_DAILY 8 // 8 +#define OWM_NUM_ALERTS 8 // OpenWeatherMaps does not specify a limit, but if you need more alerts you are probably doomed. #define OWM_NUM_AIR_POLLUTION 24 // Depending on AQI scale, hourly concentrations will need to be averaged over a period of 1h to 24h typedef struct owm_weather { - int id; // Weather condition id - String main; // Group of weather parameters (Rain, Snow, Extreme etc.) - String description; // Weather condition within the group (full list of weather conditions). Get the output in your language - String icon; // Weather icon id. + int id; // Weather condition id + String main; // Group of weather parameters (Rain, Snow, Extreme etc.) + String description; // Weather condition within the group (full list of weather conditions). Get the output in your language + String icon; // Weather icon id. } owm_weather_t; /* @@ -44,12 +44,12 @@ typedef struct owm_weather */ typedef struct owm_temp { - float morn; // Morning temperature. - float day; // Day temperature. - float eve; // Evening temperature. - float night; // Night temperature. - float min; // Min daily temperature. - float max; // Max daily temperature. + float morn; // Morning temperature. + float day; // Day temperature. + float eve; // Evening temperature. + float night; // Night temperature. + float min; // Min daily temperature. + float max; // Max daily temperature. } owm_temp_t; /* @@ -57,10 +57,10 @@ typedef struct owm_temp */ typedef struct owm_feels_like { - float morn; // Morning temperature. - float day; // Day temperature. - float eve; // Evening temperature. - float night; // Night temperature. + float morn; // Morning temperature. + float day; // Day temperature. + float eve; // Evening temperature. + float night; // Night temperature. } owm_owm_feels_like_t; /* @@ -68,23 +68,23 @@ typedef struct owm_feels_like */ typedef struct owm_current { - int64_t dt; // Current time, Unix, UTC - int64_t sunrise; // Sunrise time, Unix, UTC - int64_t sunset; // Sunset time, Unix, UTC - float temp; // Temperature. Units - default: kelvin, metric: Celsius, imperial: Fahrenheit. - float feels_like; // Temperature. This temperature parameter accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. - int pressure; // Atmospheric pressure on the sea level, hPa - int humidity; // Humidity, % - float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. - int clouds; // Cloudiness, % - float uvi; // Current UV index - int visibility; // Average visibility, metres. The maximum value of the visibility is 10km - float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - int wind_deg; // Wind direction, degrees (meteorological) - float rain_1h; // (where available) Rain volume for last hour, mm - float snow_1h; // (where available) Snow volume for last hour, mm - owm_weather_t weather; + int64_t dt; // Current time, Unix, UTC + int64_t sunrise; // Sunrise time, Unix, UTC + int64_t sunset; // Sunset time, Unix, UTC + float temp; // Temperature. Units - default: kelvin, metric: Celsius, imperial: Fahrenheit. + float feels_like; // Temperature. This temperature parameter accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. + int pressure; // Atmospheric pressure on the sea level, hPa + int humidity; // Humidity, % + float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. + int clouds; // Cloudiness, % + float uvi; // Current UV index + int visibility; // Average visibility, metres. The maximum value of the visibility is 10km + float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + int wind_deg; // Wind direction, degrees (meteorological) + float rain_1h; // (where available) Rain volume for last hour, mm + float snow_1h; // (where available) Snow volume for last hour, mm + owm_weather_t weather; } owm_current_t; /* @@ -92,8 +92,8 @@ typedef struct owm_current */ typedef struct owm_minutely { - int64_t dt; // Time of the forecasted data, unix, UTC - float precipitation; // Precipitation volume, mm + int64_t dt; // Time of the forecasted data, unix, UTC + float precipitation; // Precipitation volume, mm } owm_minutely_t; /* @@ -101,22 +101,22 @@ typedef struct owm_minutely */ typedef struct owm_hourly { - int64_t dt; // Time of the forecasted data, unix, UTC - float temp; // Temperature. Units - default: kelvin, metric: Celsius, imperial: Fahrenheit. - float feels_like; // Temperature. This temperature parameter accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. - int pressure; // Atmospheric pressure on the sea level, hPa - int humidity; // Humidity, % - float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. - int clouds; // Cloudiness, % - float uvi; // Current UV index - int visibility; // Average visibility, metres. The maximum value of the visibility is 10km - float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - int wind_deg; // Wind direction, degrees (meteorological) - float pop; // Probability of precipitation. The values of the parameter vary between 0 and 1, where 0 is equal to 0%, 1 is equal to 100% - float rain_1h; // (where available) Rain volume for last hour, mm - float snow_1h; // (where available) Snow volume for last hour, mm - owm_weather_t weather; + int64_t dt; // Time of the forecasted data, unix, UTC + float temp; // Temperature. Units - default: kelvin, metric: Celsius, imperial: Fahrenheit. + float feels_like; // Temperature. This temperature parameter accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. + int pressure; // Atmospheric pressure on the sea level, hPa + int humidity; // Humidity, % + float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. + int clouds; // Cloudiness, % + float uvi; // Current UV index + int visibility; // Average visibility, metres. The maximum value of the visibility is 10km + float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + int wind_deg; // Wind direction, degrees (meteorological) + float pop; // Probability of precipitation. The values of the parameter vary between 0 and 1, where 0 is equal to 0%, 1 is equal to 100% + float rain_1h; // (where available) Rain volume for last hour, mm + float snow_1h; // (where available) Snow volume for last hour, mm + owm_weather_t weather; } owm_hourly_t; /* @@ -124,27 +124,27 @@ typedef struct owm_hourly */ typedef struct owm_daily { - int64_t dt; // Time of the forecasted data, unix, UTC - int64_t sunrise; // Sunrise time, Unix, UTC - int64_t sunset; // Sunset time, Unix, UTC - int64_t moonrise; // The time of when the moon rises for this day, Unix, UTC - int64_t moonset; // The time of when the moon sets for this day, Unix, UTC - float moon_phase; // Moon phase. 0 and 1 are 'new moon', 0.25 is 'first quarter moon', 0.5 is 'full moon' and 0.75 is 'last quarter moon'. The periods in between are called 'waxing crescent', 'waxing gibous', 'waning gibous', and 'waning crescent', respectively. - owm_temp_t temp; - owm_owm_feels_like_t feels_like; - int pressure; // Atmospheric pressure on the sea level, hPa - int humidity; // Humidity, % - float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. - int clouds; // Cloudiness, % - float uvi; // Current UV index - int visibility; // Average visibility, metres. The maximum value of the visibility is 10km - float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. - int wind_deg; // Wind direction, degrees (meteorological) - float pop; // Probability of precipitation. The values of the parameter vary between 0 and 1, where 0 is equal to 0%, 1 is equal to 100% - float rain; // (where available) Precipitation volume, mm - float snow; // (where available) Snow volume, mm - owm_weather_t weather; + int64_t dt; // Time of the forecasted data, unix, UTC + int64_t sunrise; // Sunrise time, Unix, UTC + int64_t sunset; // Sunset time, Unix, UTC + int64_t moonrise; // The time of when the moon rises for this day, Unix, UTC + int64_t moonset; // The time of when the moon sets for this day, Unix, UTC + float moon_phase; // Moon phase. 0 and 1 are 'new moon', 0.25 is 'first quarter moon', 0.5 is 'full moon' and 0.75 is 'last quarter moon'. The periods in between are called 'waxing crescent', 'waxing gibous', 'waning gibous', and 'waning crescent', respectively. + owm_temp_t temp; + owm_owm_feels_like_t feels_like; + int pressure; // Atmospheric pressure on the sea level, hPa + int humidity; // Humidity, % + float dew_point; // Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. + int clouds; // Cloudiness, % + float uvi; // Current UV index + int visibility; // Average visibility, metres. The maximum value of the visibility is 10km + float wind_speed; // Wind speed. Wind speed. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + float wind_gust; // (where available) Wind gust. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. + int wind_deg; // Wind direction, degrees (meteorological) + float pop; // Probability of precipitation. The values of the parameter vary between 0 and 1, where 0 is equal to 0%, 1 is equal to 100% + float rain; // (where available) Precipitation volume, mm + float snow; // (where available) Snow volume, mm + owm_weather_t weather; } owm_daily_t; /* @@ -152,12 +152,12 @@ typedef struct owm_daily */ typedef struct owm_alerts { - String sender_name; // Name of the alert source. - String event; // Alert event name - int64_t start; // Date and time of the start of the alert, Unix, UTC - int64_t end; // Date and time of the end of the alert, Unix, UTC - String description; // Description of the alert - String tags; // Type of severe weather + String sender_name; // Name of the alert source. + String event; // Alert event name + int64_t start; // Date and time of the start of the alert, Unix, UTC + int64_t end; // Date and time of the end of the alert, Unix, UTC + String description; // Description of the alert + String tags; // Type of severe weather } owm_alerts_t; /* @@ -167,15 +167,15 @@ typedef struct owm_alerts */ typedef struct owm_resp_onecall { - float lat; // Geographical coordinates of the location (latitude) - float lon; // Geographical coordinates of the location (longitude) - String timezone; // Timezone name for the requested location - int timezone_offset; // Shift in seconds from UTC - owm_current_t current; + float lat; // Geographical coordinates of the location (latitude) + float lon; // Geographical coordinates of the location (longitude) + String timezone; // Timezone name for the requested location + int timezone_offset; // Shift in seconds from UTC + owm_current_t current; // owm_minutely_t minutely[OWM_NUM_MINUTELY]; - owm_hourly_t hourly[OWM_NUM_HOURLY]; - owm_daily_t daily[OWM_NUM_DAILY]; + owm_hourly_t hourly[OWM_NUM_HOURLY]; + owm_daily_t daily[OWM_NUM_DAILY]; std::vector alerts; } owm_resp_onecall_t; @@ -184,20 +184,20 @@ typedef struct owm_resp_onecall */ typedef struct owm_coord { - float lat; - float lon; + float lat; + float lon; } owm_coord_t; typedef struct owm_components { - float co[OWM_NUM_AIR_POLLUTION]; // Сoncentration of CO (Carbon monoxide), μg/m^3 - float no[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NO (Nitrogen monoxide), μg/m^3 - float no2[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NO2 (Nitrogen dioxide), μg/m^3 - float o3[OWM_NUM_AIR_POLLUTION]; // Сoncentration of O3 (Ozone), μg/m^3 - float so2[OWM_NUM_AIR_POLLUTION]; // Сoncentration of SO2 (Sulphur dioxide), μg/m^3 - float pm2_5[OWM_NUM_AIR_POLLUTION]; // Сoncentration of PM2.5 (Fine particles matter), μg/m^3 - float pm10[OWM_NUM_AIR_POLLUTION]; // Сoncentration of PM10 (Coarse particulate matter), μg/m^3 - float nh3[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NH3 (Ammonia), μg/m^3 + float co[OWM_NUM_AIR_POLLUTION]; // Сoncentration of CO (Carbon monoxide), μg/m^3 + float no[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NO (Nitrogen monoxide), μg/m^3 + float no2[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NO2 (Nitrogen dioxide), μg/m^3 + float o3[OWM_NUM_AIR_POLLUTION]; // Сoncentration of O3 (Ozone), μg/m^3 + float so2[OWM_NUM_AIR_POLLUTION]; // Сoncentration of SO2 (Sulphur dioxide), μg/m^3 + float pm2_5[OWM_NUM_AIR_POLLUTION]; // Сoncentration of PM2.5 (Fine particles matter), μg/m^3 + float pm10[OWM_NUM_AIR_POLLUTION]; // Сoncentration of PM10 (Coarse particulate matter), μg/m^3 + float nh3[OWM_NUM_AIR_POLLUTION]; // Сoncentration of NH3 (Ammonia), μg/m^3 } owm_components_t; /* @@ -205,17 +205,17 @@ typedef struct owm_components */ typedef struct owm_resp_air_pollution { - owm_coord_t coord; - int main_aqi[OWM_NUM_AIR_POLLUTION]; // Air Quality Index. Possible values: 1, 2, 3, 4, 5. Where 1 = Good, 2 = Fair, 3 = Moderate, 4 = Poor, 5 = Very Poor. + owm_coord_t coord; + int main_aqi[OWM_NUM_AIR_POLLUTION]; // Air Quality Index. Possible values: 1, 2, 3, 4, 5. Where 1 = Good, 2 = Fair, 3 = Moderate, 4 = Poor, 5 = Very Poor. owm_components_t components; - int64_t dt[OWM_NUM_AIR_POLLUTION]; // Date and time, Unix, UTC; + int64_t dt[OWM_NUM_AIR_POLLUTION]; // Date and time, Unix, UTC; } owm_resp_air_pollution_t; DeserializationError deserializeOneCall(WiFiClient &json, owm_resp_onecall_t &r); DeserializationError deserializeAirQuality(WiFiClient &json, owm_resp_air_pollution_t &r); - +DeserializationError deserializeOpenMeteoCall(WiFiClient &json, + owm_resp_onecall_t &r); #endif - diff --git a/platformio/include/client_utils.h b/platformio/include/client_utils.h index 6b84689e8..a157f3343 100644 --- a/platformio/include/client_utils.h +++ b/platformio/include/client_utils.h @@ -22,23 +22,24 @@ #include "api_response.h" #include "config.h" #ifdef USE_HTTP - #include +#include #else - #include +#include #endif wl_status_t startWiFi(int &wifiRSSI); void killWiFi(); bool waitForSNTPSync(tm *timeInfo); bool printLocalTime(tm *timeInfo); + #ifdef USE_HTTP - int getOWMonecall(WiFiClient &client, owm_resp_onecall_t &r); - int getOWMairpollution(WiFiClient &client, owm_resp_air_pollution_t &r); +int getOWMonecall(WiFiClient &client, owm_resp_onecall_t &r); +int getOWMairpollution(WiFiClient &client, owm_resp_air_pollution_t &r); +int getOMCall(WiFiClient &client, owm_resp_onecall_t &r); #else - int getOWMonecall(WiFiClientSecure &client, owm_resp_onecall_t &r); - int getOWMairpollution(WiFiClientSecure &client, owm_resp_air_pollution_t &r); -#endif - - +int getOWMonecall(WiFiClientSecure &client, owm_resp_onecall_t &r); +int getOWMairpollution(WiFiClientSecure &client, owm_resp_air_pollution_t &r); +int getOMCall(WiFiClientSecure &client, owm_resp_onecall_t &r); #endif +#endif \ No newline at end of file diff --git a/platformio/include/config.h b/platformio/include/config.h index b2dd7e8dc..698879dd1 100644 --- a/platformio/include/config.h +++ b/platformio/include/config.h @@ -21,6 +21,9 @@ #include #include +#define INDOOR 0 +#define AIR_POLLUTION 0 + // E-PAPER PANEL // This project supports the following E-Paper panels: // DISP_BW_V2 - 7.5in e-Paper (v2) 800x480px Black/White @@ -45,12 +48,12 @@ // 3 COLOR E-INK ACCENT COLOR // Defines the 3rd color to be used when a 3+ color display is selected. #if defined(DISP_3C_B) || defined(DISP_7C_F) - // #define ACCENT_COLOR GxEPD_BLACK - #define ACCENT_COLOR GxEPD_RED - // #define ACCENT_COLOR GxEPD_GREEN - // #define ACCENT_COLOR GxEPD_BLUE - // #define ACCENT_COLOR GxEPD_YELLOW - // #define ACCENT_COLOR GxEPD_ORANGE +// #define ACCENT_COLOR GxEPD_BLACK +#define ACCENT_COLOR GxEPD_RED +// #define ACCENT_COLOR GxEPD_GREEN +// #define ACCENT_COLOR GxEPD_BLUE +// #define ACCENT_COLOR GxEPD_YELLOW +// #define ACCENT_COLOR GxEPD_ORANGE #endif // LOCALE @@ -69,6 +72,9 @@ // Portuguese (Brazil) pt_BR #define LOCALE en_US +#define USE_OPEN_WEATHER_MAP +// #define USE_OPEN_METEO + // UNITS // Define exactly one macro for each measurement type below. @@ -247,7 +253,7 @@ // Extra information that can be displayed on the status bar. Set to 1 to // enable. #define STATUS_BAR_EXTRAS_BAT_VOLTAGE 0 -#define STATUS_BAR_EXTRAS_WIFI_RSSI 0 +#define STATUS_BAR_EXTRAS_WIFI_RSSI 0 // BATTERY MONITORING // You may choose to power your weather display with or without a battery. @@ -286,6 +292,7 @@ extern const unsigned HTTP_CLIENT_TCP_TIMEOUT; extern const String OWM_APIKEY; extern const String OWM_ENDPOINT; extern const String OWM_ONECALL_VERSION; +extern const String OM_ENDPOINT; extern const String LAT; extern const String LON; extern const String CITY_STRING; @@ -311,99 +318,59 @@ extern const uint32_t MAX_BATTERY_VOLTAGE; extern const uint32_t MIN_BATTERY_VOLTAGE; // CONFIG VALIDATION - DO NOT MODIFY -#if !( defined(DISP_BW_V2) \ - ^ defined(DISP_3C_B) \ - ^ defined(DISP_7C_F) \ - ^ defined(DISP_BW_V1)) - #error Invalid configuration. Exactly one display panel must be selected. +#if !(defined(DISP_BW_V2) ^ defined(DISP_3C_B) ^ defined(DISP_7C_F) ^ defined(DISP_BW_V1)) +#error Invalid configuration. Exactly one display panel must be selected. #endif -#if !( defined(DRIVER_WAVESHARE) \ - ^ defined(DRIVER_DESPI_C02)) - #error Invalid configuration. Exactly one driver board must be selected. +#if !(defined(DRIVER_WAVESHARE) ^ defined(DRIVER_DESPI_C02)) +#error Invalid configuration. Exactly one driver board must be selected. #endif #if !(defined(LOCALE)) - #error Invalid configuration. Locale not selected. +#error Invalid configuration. Locale not selected. #endif -#if !( defined(UNITS_TEMP_KELVIN) \ - ^ defined(UNITS_TEMP_CELSIUS) \ - ^ defined(UNITS_TEMP_FAHRENHEIT)) - #error Invalid configuration. Exactly one temperature unit must be selected. +#if !(defined(UNITS_TEMP_KELVIN) ^ defined(UNITS_TEMP_CELSIUS) ^ defined(UNITS_TEMP_FAHRENHEIT)) +#error Invalid configuration. Exactly one temperature unit must be selected. #endif -#if !( defined(UNITS_SPEED_METERSPERSECOND) \ - ^ defined(UNITS_SPEED_FEETPERSECOND) \ - ^ defined(UNITS_SPEED_KILOMETERSPERHOUR) \ - ^ defined(UNITS_SPEED_MILESPERHOUR) \ - ^ defined(UNITS_SPEED_KNOTS) \ - ^ defined(UNITS_SPEED_BEAUFORT)) - #error Invalid configuration. Exactly one wind speed unit must be selected. +#if !(defined(UNITS_SPEED_METERSPERSECOND) ^ defined(UNITS_SPEED_FEETPERSECOND) ^ defined(UNITS_SPEED_KILOMETERSPERHOUR) ^ defined(UNITS_SPEED_MILESPERHOUR) ^ defined(UNITS_SPEED_KNOTS) ^ defined(UNITS_SPEED_BEAUFORT)) +#error Invalid configuration. Exactly one wind speed unit must be selected. #endif -#if !( defined(UNITS_PRES_HECTOPASCALS) \ - ^ defined(UNITS_PRES_PASCALS) \ - ^ defined(UNITS_PRES_MILLIMETERSOFMERCURY) \ - ^ defined(UNITS_PRES_INCHESOFMERCURY) \ - ^ defined(UNITS_PRES_MILLIBARS) \ - ^ defined(UNITS_PRES_ATMOSPHERES) \ - ^ defined(UNITS_PRES_GRAMSPERSQUARECENTIMETER) \ - ^ defined(UNITS_PRES_POUNDSPERSQUAREINCH)) - #error Invalid configuration. Exactly one pressure unit must be selected. +#if !(defined(UNITS_PRES_HECTOPASCALS) ^ defined(UNITS_PRES_PASCALS) ^ defined(UNITS_PRES_MILLIMETERSOFMERCURY) ^ defined(UNITS_PRES_INCHESOFMERCURY) ^ defined(UNITS_PRES_MILLIBARS) ^ defined(UNITS_PRES_ATMOSPHERES) ^ defined(UNITS_PRES_GRAMSPERSQUARECENTIMETER) ^ defined(UNITS_PRES_POUNDSPERSQUAREINCH)) +#error Invalid configuration. Exactly one pressure unit must be selected. #endif -#if !( defined(UNITS_DIST_KILOMETERS) \ - ^ defined(UNITS_DIST_MILES)) - #error Invalid configuration. Exactly one distance unit must be selected. +#if !(defined(UNITS_DIST_KILOMETERS) ^ defined(UNITS_DIST_MILES)) +#error Invalid configuration. Exactly one distance unit must be selected. #endif -#if !( defined(UNITS_HOURLY_PRECIP_POP) \ - ^ defined(UNITS_HOURLY_PRECIP_MILLIMETERS) \ - ^ defined(UNITS_HOURLY_PRECIP_CENTIMETERS) \ - ^ defined(UNITS_HOURLY_PRECIP_INCHES)) - #error Invalid configuration. Exactly one houly precipitation measurement must be selected. +#if !(defined(UNITS_HOURLY_PRECIP_POP) ^ defined(UNITS_HOURLY_PRECIP_MILLIMETERS) ^ defined(UNITS_HOURLY_PRECIP_CENTIMETERS) ^ defined(UNITS_HOURLY_PRECIP_INCHES)) +#error Invalid configuration. Exactly one houly precipitation measurement must be selected. #endif -#if !( defined(UNITS_DAILY_PRECIP_POP) \ - ^ defined(UNITS_DAILY_PRECIP_MILLIMETERS) \ - ^ defined(UNITS_DAILY_PRECIP_CENTIMETERS) \ - ^ defined(UNITS_DAILY_PRECIP_INCHES)) - #error Invalid configuration. Exactly one daily precipitation measurement must be selected. +#if !(defined(UNITS_DAILY_PRECIP_POP) ^ defined(UNITS_DAILY_PRECIP_MILLIMETERS) ^ defined(UNITS_DAILY_PRECIP_CENTIMETERS) ^ defined(UNITS_DAILY_PRECIP_INCHES)) +#error Invalid configuration. Exactly one daily precipitation measurement must be selected. #endif -#if !( defined(USE_HTTP) \ - ^ defined(USE_HTTPS_NO_CERT_VERIF) \ - ^ defined(USE_HTTPS_WITH_CERT_VERIF)) - #error Invalid configuration. Exactly one HTTP mode must be selected. +#if !(defined(USE_HTTP) ^ defined(USE_HTTPS_NO_CERT_VERIF) ^ defined(USE_HTTPS_WITH_CERT_VERIF)) +#error Invalid configuration. Exactly one HTTP mode must be selected. #endif -#if !( defined(WIND_INDICATOR_ARROW) \ - || ( \ - defined(WIND_INDICATOR_NUMBER) \ - ^ defined(WIND_INDICATOR_CPN_CARDINAL) \ - ^ defined(WIND_INDICATOR_CPN_INTERCARDINAL) \ - ^ defined(WIND_INDICATOR_CPN_SECONDARY_INTERCARDINAL) \ - ^ defined(WIND_INDICATOR_CPN_TERTIARY_INTERCARDINAL) \ - ) \ - ^ defined(WIND_INDICATOR_NONE)) - #error Invalid configuration. Illegal selction of wind indicator(s). +#if !(defined(WIND_INDICATOR_ARROW) || (defined(WIND_INDICATOR_NUMBER) ^ defined(WIND_INDICATOR_CPN_CARDINAL) ^ defined(WIND_INDICATOR_CPN_INTERCARDINAL) ^ defined(WIND_INDICATOR_CPN_SECONDARY_INTERCARDINAL) ^ defined(WIND_INDICATOR_CPN_TERTIARY_INTERCARDINAL)) ^ defined(WIND_INDICATOR_NONE)) +#error Invalid configuration. Illegal selction of wind indicator(s). #endif -#if defined(WIND_INDICATOR_ARROW) \ - && !( defined(WIND_ICONS_CARDINAL) \ - ^ defined(WIND_ICONS_INTERCARDINAL) \ - ^ defined(WIND_ICONS_SECONDARY_INTERCARDINAL) \ - ^ defined(WIND_ICONS_TERTIARY_INTERCARDINAL) \ - ^ defined(WIND_ICONS_360)) - #error Invalid configuration. Exactly one wind direction icon precision level must be selected. +#if defined(WIND_INDICATOR_ARROW) && !(defined(WIND_ICONS_CARDINAL) ^ defined(WIND_ICONS_INTERCARDINAL) ^ defined(WIND_ICONS_SECONDARY_INTERCARDINAL) ^ defined(WIND_ICONS_TERTIARY_INTERCARDINAL) ^ defined(WIND_ICONS_360)) +#error Invalid configuration. Exactly one wind direction icon precision level must be selected. #endif #if !(defined(FONT_HEADER)) - #error Invalid configuration. Font not selected. +#error Invalid configuration. Font not selected. #endif #if !(defined(DISPLAY_DAILY_PRECIP)) - #error Invalid configuration. DISPLAY_DAILY_PRECIP not defined. +#error Invalid configuration. DISPLAY_DAILY_PRECIP not defined. #endif #if !(defined(DISPLAY_HOURLY_ICONS)) - #error Invalid configuration. DISPLAY_HOURLY_ICONS not defined. +#error Invalid configuration. DISPLAY_HOURLY_ICONS not defined. #endif #if !(defined(DISPLAY_ALERTS)) - #error Invalid configuration. DISPLAY_ALERTS not defined. +#error Invalid configuration. DISPLAY_ALERTS not defined. #endif #if !(defined(BATTERY_MONITORING)) - #error Invalid configuration. BATTERY_MONITORING not defined. +#error Invalid configuration. BATTERY_MONITORING not defined. #endif #if !(defined(DEBUG_LEVEL)) - #error Invalid configuration. DEBUG_LEVEL not defined. +#error Invalid configuration. DEBUG_LEVEL not defined. #endif #endif diff --git a/platformio/include/credentials.h.template b/platformio/include/credentials.h.template new file mode 100644 index 000000000..7a1a241b7 --- /dev/null +++ b/platformio/include/credentials.h.template @@ -0,0 +1,14 @@ +// WIFI +const char *WIFI_SSID = "ssid"; +const char *WIFI_PASSWORD = "password"; + +// OPENWEATHERMAP API +const String OWM_APIKEY = "abcdefghijklmnopqrstuvwxyz012345"; + +// LOCATION +// Set your latitude and longitude. +// (used to get weather data as part of API requests to OpenWeatherMap) +const String LAT = "40.7128"; +const String LON = "-74.0060"; +// City name that will be shown in the top-right corner of the display. +const String CITY_STRING = "New York"; \ No newline at end of file diff --git a/platformio/platformio.ini b/platformio/platformio.ini index 2d34e8c5c..8dff51440 100644 --- a/platformio/platformio.ini +++ b/platformio/platformio.ini @@ -8,13 +8,9 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html - [platformio] default_envs = dfrobot_firebeetle2_esp32e -; default_envs = firebeetle32 - -; default options for each '[env:**]' [env] platform = espressif32 @ 6.9.0 framework = arduino @@ -31,18 +27,25 @@ lib_deps = [env:dfrobot_firebeetle2_esp32e] board = dfrobot_firebeetle2_esp32e monitor_speed = 115200 -; override default partition table -; https://github.com/espressif/arduino-esp32/tree/master/tools/partitions board_build.partitions = huge_app.csv -; change MCU frequency, 240MHz -> 80MHz (for better power efficiency) board_build.f_cpu = 80000000L - [env:firebeetle32] board = firebeetle32 monitor_speed = 115200 ; override default partition table ; https://github.com/espressif/arduino-esp32/tree/master/tools/partitions board_build.partitions = huge_app.csv -; change MCU frequency, 240MHz -> 80MHz (for better power efficiency) board_build.f_cpu = 80000000L + +[env:esp32] +board = esp32dev +monitor_speed = 115200 +board_build.partitions = huge_app.csv +board_build.f_cpu = 80000000L + + +[env:esp32c3pico] +board = lolin_c3_mini +monitor_speed = 115200 +board_build.partitions = huge_app.csv diff --git a/platformio/src/api_response.cpp b/platformio/src/api_response.cpp index 27f7b9a32..f6c81f56d 100644 --- a/platformio/src/api_response.cpp +++ b/platformio/src/api_response.cpp @@ -26,68 +26,68 @@ DeserializationError deserializeOneCall(WiFiClient &json, int i; JsonDocument filter; - filter["current"] = true; + filter["current"] = true; filter["minutely"] = false; - filter["hourly"] = true; - filter["daily"] = true; + filter["hourly"] = true; + filter["daily"] = true; #if !DISPLAY_ALERTS - filter["alerts"] = false; + filter["alerts"] = false; #else // description can be very long so they are filtered out to save on memory // along with sender_name for (int i = 0; i < OWM_NUM_ALERTS; ++i) { filter["alerts"][i]["sender_name"] = false; - filter["alerts"][i]["event"] = true; - filter["alerts"][i]["start"] = true; - filter["alerts"][i]["end"] = true; + filter["alerts"][i]["event"] = true; + filter["alerts"][i]["start"] = true; + filter["alerts"][i]["end"] = true; filter["alerts"][i]["description"] = false; - filter["alerts"][i]["tags"] = true; + filter["alerts"][i]["tags"] = true; } #endif JsonDocument doc; DeserializationError error = deserializeJson(doc, json, - DeserializationOption::Filter(filter)); + DeserializationOption::Filter(filter)); #if DEBUG_LEVEL >= 1 - Serial.println("[debug] doc.overflowed() : " - + String(doc.overflowed())); + Serial.println("[debug] doc.overflowed() : " + String(doc.overflowed())); #endif #if DEBUG_LEVEL >= 2 serializeJsonPretty(doc, Serial); #endif - if (error) { + if (error) + { return error; } - r.lat = doc["lat"] .as(); - r.lon = doc["lon"] .as(); - r.timezone = doc["timezone"] .as(); + r.lat = doc["lat"].as(); + r.lon = doc["lon"].as(); + r.timezone = doc["timezone"].as(); r.timezone_offset = doc["timezone_offset"].as(); JsonObject current = doc["current"]; - r.current.dt = current["dt"] .as(); - r.current.sunrise = current["sunrise"] .as(); - r.current.sunset = current["sunset"] .as(); - r.current.temp = current["temp"] .as(); + r.current.dt = current["dt"].as(); + r.current.sunrise = current["sunrise"].as(); + r.current.sunset = current["sunset"].as(); + r.current.temp = current["temp"].as(); r.current.feels_like = current["feels_like"].as(); - r.current.pressure = current["pressure"] .as(); - r.current.humidity = current["humidity"] .as(); - r.current.dew_point = current["dew_point"] .as(); - r.current.clouds = current["clouds"] .as(); - r.current.uvi = current["uvi"] .as(); + r.current.pressure = current["pressure"].as(); + r.current.humidity = current["humidity"].as(); + r.current.dew_point = current["dew_point"].as(); + r.current.clouds = current["clouds"].as(); + r.current.uvi = current["uvi"].as(); r.current.visibility = current["visibility"].as(); r.current.wind_speed = current["wind_speed"].as(); - r.current.wind_gust = current["wind_gust"] .as(); - r.current.wind_deg = current["wind_deg"] .as(); - r.current.rain_1h = current["rain"]["1h"].as(); - r.current.snow_1h = current["snow"]["1h"].as(); + r.current.wind_gust = current["wind_gust"].as(); + r.current.wind_deg = current["wind_deg"].as(); + r.current.rain_1h = current["rain"]["1h"].as(); + r.current.snow_1h = current["snow"]["1h"].as(); JsonObject current_weather = current["weather"][0]; - r.current.weather.id = current_weather["id"] .as(); - r.current.weather.main = current_weather["main"] .as(); + r.current.weather.id = current_weather["id"].as(); + r.current.weather.main = current_weather["main"].as(); r.current.weather.description = current_weather["description"].as(); - r.current.weather.icon = current_weather["icon"] .as(); + r.current.weather.icon = current_weather["icon"].as(); // minutely forecast is currently unused // i = 0; @@ -106,26 +106,26 @@ DeserializationError deserializeOneCall(WiFiClient &json, i = 0; for (JsonObject hourly : doc["hourly"].as()) { - r.hourly[i].dt = hourly["dt"] .as(); - r.hourly[i].temp = hourly["temp"] .as(); + r.hourly[i].dt = hourly["dt"].as(); + r.hourly[i].temp = hourly["temp"].as(); r.hourly[i].feels_like = hourly["feels_like"].as(); - r.hourly[i].pressure = hourly["pressure"] .as(); - r.hourly[i].humidity = hourly["humidity"] .as(); - r.hourly[i].dew_point = hourly["dew_point"] .as(); - r.hourly[i].clouds = hourly["clouds"] .as(); - r.hourly[i].uvi = hourly["uvi"] .as(); + r.hourly[i].pressure = hourly["pressure"].as(); + r.hourly[i].humidity = hourly["humidity"].as(); + r.hourly[i].dew_point = hourly["dew_point"].as(); + r.hourly[i].clouds = hourly["clouds"].as(); + r.hourly[i].uvi = hourly["uvi"].as(); r.hourly[i].visibility = hourly["visibility"].as(); r.hourly[i].wind_speed = hourly["wind_speed"].as(); - r.hourly[i].wind_gust = hourly["wind_gust"] .as(); - r.hourly[i].wind_deg = hourly["wind_deg"] .as(); - r.hourly[i].pop = hourly["pop"] .as(); - r.hourly[i].rain_1h = hourly["rain"]["1h"].as(); - r.hourly[i].snow_1h = hourly["snow"]["1h"].as(); + r.hourly[i].wind_gust = hourly["wind_gust"].as(); + r.hourly[i].wind_deg = hourly["wind_deg"].as(); + r.hourly[i].pop = hourly["pop"].as(); + r.hourly[i].rain_1h = hourly["rain"]["1h"].as(); + r.hourly[i].snow_1h = hourly["snow"]["1h"].as(); JsonObject hourly_weather = hourly["weather"][0]; - r.hourly[i].weather.id = hourly_weather["id"] .as(); - r.hourly[i].weather.main = hourly_weather["main"] .as(); + r.hourly[i].weather.id = hourly_weather["id"].as(); + r.hourly[i].weather.main = hourly_weather["main"].as(); r.hourly[i].weather.description = hourly_weather["description"].as(); - r.hourly[i].weather.icon = hourly_weather["icon"] .as(); + r.hourly[i].weather.icon = hourly_weather["icon"].as(); if (i == OWM_NUM_HOURLY - 1) { @@ -137,41 +137,41 @@ DeserializationError deserializeOneCall(WiFiClient &json, i = 0; for (JsonObject daily : doc["daily"].as()) { - r.daily[i].dt = daily["dt"] .as(); - r.daily[i].sunrise = daily["sunrise"] .as(); - r.daily[i].sunset = daily["sunset"] .as(); - r.daily[i].moonrise = daily["moonrise"] .as(); - r.daily[i].moonset = daily["moonset"] .as(); + r.daily[i].dt = daily["dt"].as(); + r.daily[i].sunrise = daily["sunrise"].as(); + r.daily[i].sunset = daily["sunset"].as(); + r.daily[i].moonrise = daily["moonrise"].as(); + r.daily[i].moonset = daily["moonset"].as(); r.daily[i].moon_phase = daily["moon_phase"].as(); JsonObject daily_temp = daily["temp"]; - r.daily[i].temp.morn = daily_temp["morn"] .as(); - r.daily[i].temp.day = daily_temp["day"] .as(); - r.daily[i].temp.eve = daily_temp["eve"] .as(); + r.daily[i].temp.morn = daily_temp["morn"].as(); + r.daily[i].temp.day = daily_temp["day"].as(); + r.daily[i].temp.eve = daily_temp["eve"].as(); r.daily[i].temp.night = daily_temp["night"].as(); - r.daily[i].temp.min = daily_temp["min"] .as(); - r.daily[i].temp.max = daily_temp["max"] .as(); + r.daily[i].temp.min = daily_temp["min"].as(); + r.daily[i].temp.max = daily_temp["max"].as(); JsonObject daily_feels_like = daily["feels_like"]; - r.daily[i].feels_like.morn = daily_feels_like["morn"] .as(); - r.daily[i].feels_like.day = daily_feels_like["day"] .as(); - r.daily[i].feels_like.eve = daily_feels_like["eve"] .as(); + r.daily[i].feels_like.morn = daily_feels_like["morn"].as(); + r.daily[i].feels_like.day = daily_feels_like["day"].as(); + r.daily[i].feels_like.eve = daily_feels_like["eve"].as(); r.daily[i].feels_like.night = daily_feels_like["night"].as(); - r.daily[i].pressure = daily["pressure"] .as(); - r.daily[i].humidity = daily["humidity"] .as(); - r.daily[i].dew_point = daily["dew_point"] .as(); - r.daily[i].clouds = daily["clouds"] .as(); - r.daily[i].uvi = daily["uvi"] .as(); + r.daily[i].pressure = daily["pressure"].as(); + r.daily[i].humidity = daily["humidity"].as(); + r.daily[i].dew_point = daily["dew_point"].as(); + r.daily[i].clouds = daily["clouds"].as(); + r.daily[i].uvi = daily["uvi"].as(); r.daily[i].visibility = daily["visibility"].as(); r.daily[i].wind_speed = daily["wind_speed"].as(); - r.daily[i].wind_gust = daily["wind_gust"] .as(); - r.daily[i].wind_deg = daily["wind_deg"] .as(); - r.daily[i].pop = daily["pop"] .as(); - r.daily[i].rain = daily["rain"] .as(); - r.daily[i].snow = daily["snow"] .as(); + r.daily[i].wind_gust = daily["wind_gust"].as(); + r.daily[i].wind_deg = daily["wind_deg"].as(); + r.daily[i].pop = daily["pop"].as(); + r.daily[i].rain = daily["rain"].as(); + r.daily[i].snow = daily["snow"].as(); JsonObject daily_weather = daily["weather"][0]; - r.daily[i].weather.id = daily_weather["id"] .as(); - r.daily[i].weather.main = daily_weather["main"] .as(); + r.daily[i].weather.id = daily_weather["id"].as(); + r.daily[i].weather.main = daily_weather["main"].as(); r.daily[i].weather.description = daily_weather["description"].as(); - r.daily[i].weather.icon = daily_weather["icon"] .as(); + r.daily[i].weather.icon = daily_weather["icon"].as(); if (i == OWM_NUM_DAILY - 1) { @@ -186,11 +186,11 @@ DeserializationError deserializeOneCall(WiFiClient &json, { owm_alerts_t new_alert = {}; // new_alert.sender_name = alerts["sender_name"].as(); - new_alert.event = alerts["event"] .as(); - new_alert.start = alerts["start"] .as(); - new_alert.end = alerts["end"] .as(); + new_alert.event = alerts["event"].as(); + new_alert.start = alerts["start"].as(); + new_alert.end = alerts["end"].as(); // new_alert.description = alerts["description"].as(); - new_alert.tags = alerts["tags"][0] .as(); + new_alert.tags = alerts["tags"][0].as(); r.alerts.push_back(new_alert); if (i == OWM_NUM_ALERTS - 1) @@ -213,13 +213,13 @@ DeserializationError deserializeAirQuality(WiFiClient &json, DeserializationError error = deserializeJson(doc, json); #if DEBUG_LEVEL >= 1 - Serial.println("[debug] doc.overflowed() : " - + String(doc.overflowed())); + Serial.println("[debug] doc.overflowed() : " + String(doc.overflowed())); #endif #if DEBUG_LEVEL >= 2 serializeJsonPretty(doc, Serial); #endif - if (error) { + if (error) + { return error; } @@ -232,14 +232,14 @@ DeserializationError deserializeAirQuality(WiFiClient &json, r.main_aqi[i] = list["main"]["aqi"].as(); JsonObject list_components = list["components"]; - r.components.co[i] = list_components["co"].as(); - r.components.no[i] = list_components["no"].as(); - r.components.no2[i] = list_components["no2"].as(); - r.components.o3[i] = list_components["o3"].as(); - r.components.so2[i] = list_components["so2"].as(); + r.components.co[i] = list_components["co"].as(); + r.components.no[i] = list_components["no"].as(); + r.components.no2[i] = list_components["no2"].as(); + r.components.o3[i] = list_components["o3"].as(); + r.components.so2[i] = list_components["so2"].as(); r.components.pm2_5[i] = list_components["pm2_5"].as(); - r.components.pm10[i] = list_components["pm10"].as(); - r.components.nh3[i] = list_components["nh3"].as(); + r.components.pm10[i] = list_components["pm10"].as(); + r.components.nh3[i] = list_components["nh3"].as(); r.dt[i] = list["dt"].as(); @@ -253,3 +253,188 @@ DeserializationError deserializeAirQuality(WiFiClient &json, return error; } // end deserializeAirQuality +DeserializationError deserializeOpenMeteoCall(WiFiClient &json, + owm_resp_onecall_t &r) +{ + JsonDocument filter; + filter["current"] = true; + filter["minutely"] = false; + filter["hourly"] = true; + filter["daily"] = true; +#if !DISPLAY_ALERTS + filter["alerts"] = false; +#else + // description can be very long so they are filtered out to save on memory + // along with sender_name + for (int i = 0; i < OWM_NUM_ALERTS; ++i) + { + filter["alerts"][i]["sender_name"] = false; + filter["alerts"][i]["event"] = true; + filter["alerts"][i]["start"] = true; + filter["alerts"][i]["end"] = true; + filter["alerts"][i]["description"] = false; + filter["alerts"][i]["tags"] = true; + } +#endif + + JsonDocument doc; + + DeserializationError error = deserializeJson(doc, json, DeserializationOption::Filter(filter)); + +#if DEBUG_LEVEL >= 1 + Serial.println("[debug] doc.overflowed() : " + String(doc.overflowed())); +#endif +#if DEBUG_LEVEL >= 2 + serializeJsonPretty(doc, Serial); +#endif + if (error) + { + return error; + } + + r.lat = doc["latitude"].as(); + r.lon = doc["longitude"].as(); + r.timezone = doc["timezone"].as(); + r.timezone_offset = doc["utc_offset_seconds"].as(); + + JsonObject current = doc["current"]; + JsonObject daily = doc["daily"]; + JsonObject hourly = doc["hourly"]; + + r.current.dt = current["time"].as(); + r.current.sunrise = daily["sunrise"][0].as(); // + r.current.sunset = daily["sunset"][0].as(); // + r.current.temp = current["temperature_2m"].as(); + r.current.feels_like = current["apparent_temperature"].as(); + r.current.pressure = current["surface_pressure"].as(); // + r.current.humidity = current["relative_humidity_2m"].as(); + // r.current.dew_point = current["dew_point"] .as(); // + r.current.clouds = current["cloud_cover"].as(); + r.current.uvi = daily["uv_index_max"][0].as(); // + r.current.visibility = hourly["visibility"][0].as(); // + r.current.wind_speed = current["wind_speed_10m"].as(); + r.current.wind_gust = current["wind_gusts_10m"].as(); + r.current.wind_deg = current["wind_direction_10m"].as(); // w + r.current.rain_1h = current["rain"].as(); + r.current.snow_1h = current["snow"].as(); + // JsonObject current_weather = current["weather"][0]; + r.current.weather.id = current["weather_code"].as(); + // r.current.weather.main = current_weather["main"] .as(); + // r.current.weather.description = current_weather["description"].as(); + // r.current.weather.icon = current_weather["icon"] .as(); + + // minutely forecast is currently unused + // i = 0; + // for (JsonObject minutely : doc["minutely"].as()) + // { + // r.minutely[i].dt = minutely["dt"] .as(); + // r.minutely[i].precipitation = minutely["precipitation"].as(); + + // if (i == OWM_NUM_MINUTELY - 1) + // { + // break; + // } + // ++i; + // } + + int hours = doc["hourly"]["time"].size(); + for (size_t i = 0; i < hours; i++) + { + r.hourly[i].dt = hourly["time"][i].as(); // dt means + r.hourly[i].temp = hourly["temperature_2m"][i].as(); + r.hourly[i].feels_like = hourly["apparent_temperature"][i].as(); + r.hourly[i].pressure = hourly["surface_pressure"][i].as(); + r.hourly[i].humidity = hourly["relative_humidity_2m"][i].as(); + r.hourly[i].dew_point = hourly["dew_point_2m"][i].as(); + r.hourly[i].clouds = hourly["cloud_cover"][i].as(); + // r.hourly[i].uvi = hourly["uvi"][i] .as(); + r.hourly[i].visibility = hourly["visibility"][i].as(); + r.hourly[i].wind_speed = hourly["wind_speed_10m"][i].as(); + r.hourly[i].wind_gust = hourly["wind_gust_10m"][i].as(); + r.hourly[i].wind_deg = hourly["wind_deg_10m"][i].as(); + r.hourly[i].pop = hourly["precipitation_probability"][i].as(); + r.hourly[i].rain_1h = hourly["rain"][i].as(); + r.hourly[i].snow_1h = hourly["snowfall"][i].as(); + // JsonObject hourly_weather = hourly["weather"][0]; + // r.hourly[i].weather.id = hourly_weather["id"] .as(); + // r.hourly[i].weather.main = hourly_weather["main"] .as(); + // r.hourly[i].weather.description = hourly_weather["description"].as(); + // r.hourly[i].weather.icon = hourly_weather["icon"] .as(); + + if (i == OWM_NUM_HOURLY - 1) + { + break; + } + } + + int days = doc["daily"]["time"].size(); + for (size_t i = 0; i < days; i++) + { + r.daily[i].dt = daily["time"][i].as(); + r.daily[i].sunrise = daily["sunrise"][i].as(); + r.daily[i].sunset = daily["sunset"][i].as(); + // r.daily[i].moonrise = daily["moonrise"] .as(); + // r.daily[i].moonset = daily["moonset"] .as(); + // r.daily[i].moon_phase = daily["moon_phase"].as(); + // JsonObject daily_temp = daily["temp"]; + // r.daily[i].temp.morn = daily_temp["morn"] .as(); + // r.daily[i].temp.day = daily_temp["day"] .as(); + // r.daily[i].temp.eve = daily_temp["eve"] .as(); + // r.daily[i].temp.night = daily_temp["night"].as(); + r.daily[i].temp.min = daily["temperature_2m_min"][i].as(); + r.daily[i].temp.max = daily["temperature_2m_max"][i].as(); + Serial.println("daily temp min: " + String(r.daily[i].temp.min)); + Serial.println("daily temp max: " + String(r.daily[i].temp.max)); + // JsonObject daily_feels_like = daily["feels_like"]; + // r.daily[i].feels_like.morn = daily_feels_like["morn"] .as(); + // r.daily[i].feels_like.day = daily_feels_like["day"] .as(); + // r.daily[i].feels_like.eve = daily_feels_like["eve"] .as(); + // r.daily[i].feels_like.night = daily_feels_like["night"].as(); + r.daily[i].pressure = daily["pressure"].as(); + r.daily[i].humidity = daily["humidity"].as(); + r.daily[i].dew_point = daily["dew_point"].as(); + r.daily[i].clouds = daily["clouds"].as(); + r.daily[i].uvi = daily["uvi"].as(); + r.daily[i].visibility = daily["visibility"].as(); + r.daily[i].wind_speed = daily["wind_speed"].as(); + r.daily[i].wind_gust = daily["wind_gust"].as(); + r.daily[i].wind_deg = daily["wind_deg"].as(); + r.daily[i].pop = daily["pop"].as(); + r.daily[i].rain = daily["rain"].as(); + r.daily[i].snow = daily["snow"].as(); + // JsonObject daily_weather = daily["weather"][0]; + r.daily[i].weather.id = daily["weather_code"][i].as(); + Serial.println("daily weather id: " + String(r.daily[i].weather.id)); + // r.daily[i].weather.main = daily_weather["main"] .as(); + // r.daily[i].weather.description = daily_weather["description"].as(); + // r.daily[i].weather.icon = daily_weather["icon"] .as(); + + if (i == OWM_NUM_DAILY - 1) + { + break; + } + } + +#if DISPLAY_ALERTS + i = 0; + for (JsonObject alerts : doc["alerts"].as()) + { + owm_alerts_t new_alert = {}; + // new_alert.sender_name = alerts["sender_name"].as(); + new_alert.event = alerts["event"].as(); + new_alert.start = alerts["start"].as(); + new_alert.end = alerts["end"].as(); + // new_alert.description = alerts["description"].as(); + new_alert.tags = alerts["tags"][0].as(); + r.alerts.push_back(new_alert); + + if (i == OWM_NUM_ALERTS - 1) + { + break; + } + ++i; + } +#endif + + return error; +} // end deserializeOpenMeteoCall \ No newline at end of file diff --git a/platformio/src/client_utils.cpp b/platformio/src/client_utils.cpp index 36b5a6cbf..753d0a8e9 100644 --- a/platformio/src/client_utils.cpp +++ b/platformio/src/client_utils.cpp @@ -40,13 +40,13 @@ #include "display_utils.h" #include "renderer.h" #ifndef USE_HTTP - #include +#include #endif #ifdef USE_HTTP - static const uint16_t OWM_PORT = 80; +static const uint16_t PORT = 80; #else - static const uint16_t OWM_PORT = 443; +static const uint16_t PORT = 443; #endif /* Power-on and connect WiFi. @@ -121,13 +121,11 @@ bool waitForSNTPSync(tm *timeInfo) { // Wait for SNTP synchronization to complete unsigned long timeout = millis() + NTP_TIMEOUT; - if ((sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) - && (millis() < timeout)) + if ((sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) && (millis() < timeout)) { Serial.print(TXT_WAITING_FOR_SNTP); delay(100); // ms - while ((sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) - && (millis() < timeout)) + while ((sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) && (millis() < timeout)) { Serial.print("."); delay(100); // ms @@ -144,17 +142,15 @@ bool waitForSNTPSync(tm *timeInfo) * Returns the HTTP Status Code. */ #ifdef USE_HTTP - int getOWMonecall(WiFiClient &client, owm_resp_onecall_t &r) +int getOWMonecall(WiFiClient &client, owm_resp_onecall_t &r) #else - int getOWMonecall(WiFiClientSecure &client, owm_resp_onecall_t &r) +int getOWMonecall(WiFiClientSecure &client, owm_resp_onecall_t &r) #endif { int attempts = 0; bool rxSuccess = false; DeserializationError jsonErr = {}; - String uri = "/data/" + OWM_ONECALL_VERSION - + "/onecall?lat=" + LAT + "&lon=" + LON + "&lang=" + OWM_LANG - + "&units=standard&exclude=minutely"; + String uri = "/data/" + OWM_ONECALL_VERSION + "/onecall?lat=" + LAT + "&lon=" + LON + "&lang=" + OWM_LANG + "&units=standard&exclude=minutely"; #if !DISPLAY_ALERTS // exclude alerts uri += ",alerts"; @@ -180,8 +176,8 @@ bool waitForSNTPSync(tm *timeInfo) HTTPClient http; http.setConnectTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms - http.setTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms - http.begin(client, OWM_ENDPOINT, OWM_PORT, uri); + http.setTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms + http.begin(client, OWM_ENDPOINT, PORT, uri); httpResponse = http.GET(); if (httpResponse == HTTP_CODE_OK) { @@ -195,8 +191,7 @@ bool waitForSNTPSync(tm *timeInfo) } client.stop(); http.end(); - Serial.println(" " + String(httpResponse, DEC) + " " - + getHttpResponsePhrase(httpResponse)); + Serial.println(" " + String(httpResponse, DEC) + " " + getHttpResponsePhrase(httpResponse)); ++attempts; } @@ -210,9 +205,9 @@ bool waitForSNTPSync(tm *timeInfo) * Returns the HTTP Status Code. */ #ifdef USE_HTTP - int getOWMairpollution(WiFiClient &client, owm_resp_air_pollution_t &r) +int getOWMairpollution(WiFiClient &client, owm_resp_air_pollution_t &r) #else - int getOWMairpollution(WiFiClientSecure &client, owm_resp_air_pollution_t &r) +int getOWMairpollution(WiFiClientSecure &client, owm_resp_air_pollution_t &r) #endif { int attempts = 0; @@ -229,15 +224,11 @@ bool waitForSNTPSync(tm *timeInfo) char startStr[22]; sprintf(endStr, "%lld", end); sprintf(startStr, "%lld", start); - String uri = "/data/2.5/air_pollution/history?lat=" + LAT + "&lon=" + LON - + "&start=" + startStr + "&end=" + endStr - + "&appid=" + OWM_APIKEY; + String uri = "/data/2.5/air_pollution/history?lat=" + LAT + "&lon=" + LON + "&start=" + startStr + "&end=" + endStr + "&appid=" + OWM_APIKEY; // This string is printed to terminal to help with debugging. The API key is // censored to reduce the risk of users exposing their key. String sanitizedUri = OWM_ENDPOINT + - "/data/2.5/air_pollution/history?lat=" + LAT + "&lon=" + LON - + "&start=" + startStr + "&end=" + endStr - + "&appid={API key}"; + "/data/2.5/air_pollution/history?lat=" + LAT + "&lon=" + LON + "&start=" + startStr + "&end=" + endStr + "&appid={API key}"; Serial.print(TXT_ATTEMPTING_HTTP_REQ); Serial.println(": " + sanitizedUri); @@ -253,8 +244,8 @@ bool waitForSNTPSync(tm *timeInfo) HTTPClient http; http.setConnectTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms - http.setTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms - http.begin(client, OWM_ENDPOINT, OWM_PORT, uri); + http.setTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms + http.begin(client, OWM_ENDPOINT, PORT, uri); httpResponse = http.GET(); if (httpResponse == HTTP_CODE_OK) { @@ -268,25 +259,87 @@ bool waitForSNTPSync(tm *timeInfo) } client.stop(); http.end(); - Serial.println(" " + String(httpResponse, DEC) + " " - + getHttpResponsePhrase(httpResponse)); + Serial.println(" " + String(httpResponse, DEC) + " " + getHttpResponsePhrase(httpResponse)); ++attempts; } return httpResponse; } // getOWMairpollution +/* Perform an HTTP GET request to OpenMeteo's API + * If data is received, it will be parsed and stored in the global variable + * om_call. + * + * Returns the HTTP Status Code. + */ +#ifdef USE_HTTP +int getOMcall(WiFiClient &client, owm_resp_onecall_t &r) +#else +int getOMCall(WiFiClientSecure &client, owm_resp_onecall_t &r) +#endif +{ + int attempts = 0; + bool rxSuccess = false; + DeserializationError jsonErr = {}; + + String uri = "/v1/forecast?latitude=" + LAT + "&longitude=" + LON + "&" + + "current=temperature_2m,relative_humidity_2m,apparent_temperature,rain,snowfall,weather_code,cloud_cover,surface_pressure,wind_speed_10m,wind_direction_10m,wind_gusts_10m&" + + "hourly=temperature_2m,precipitation_probability,rain,snowfall&" + + "daily=weather_code,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,sunrise,sunset,daylight_duration,sunshine_duration,uv_index_max,uv_index_clear_sky_max,precipitation_sum,rain_sum,showers_sum,snowfall_sum,precipitation_hours,precipitation_probability_max,wind_speed_10m_max,wind_gusts_10m_max,wind_direction_10m_dominant,shortwave_radiation_sum,et0_fao_evapotranspiration&" + + "timezone=Europe%2FBerlin&timeformat=unixtime"; + // https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060¤t=temperature_2m,relative_humidity_2m,apparent_temperature,rain,snowfall,weather_code,cloud_cover,surface_pressure,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=dew_point_2m,visibility&daily=sunrise,sunset,uv_index_max&timezone=Europe%2FBerlin + +#if !DISPLAY_ALERTS + // exclude alerts + // uri += ",alerts"; +#endif + + // This string is printed to terminal to help with debugging. + String sanitizedUri = OM_ENDPOINT + uri; + + Serial.print(TXT_ATTEMPTING_HTTP_REQ); + Serial.println(": " + sanitizedUri); + int httpResponse = 0; + while (!rxSuccess && attempts < 3) + { + wl_status_t connection_status = WiFi.status(); + if (connection_status != WL_CONNECTED) + { + // -512 offset distinguishes these errors from httpClient errors + return -512 - static_cast(connection_status); + } + + HTTPClient http; + http.setConnectTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms + http.setTimeout(HTTP_CLIENT_TCP_TIMEOUT); // default 5000ms + http.begin(client, OM_ENDPOINT, PORT, uri); + httpResponse = http.GET(); + if (httpResponse == HTTP_CODE_OK) + { + jsonErr = deserializeOpenMeteoCall(http.getStream(), r); // Convert String to const char* + if (jsonErr) + { + // -256 offset distinguishes these errors from httpClient errors + httpResponse = -256 - static_cast(jsonErr.code()); + } + rxSuccess = !jsonErr; + } + client.stop(); + http.end(); + Serial.println(" " + String(httpResponse, DEC) + " " + getHttpResponsePhrase(httpResponse)); + ++attempts; + } + + return httpResponse; +} // getOMcall + /* Prints debug information about heap usage. */ -void printHeapUsage() { - Serial.println("[debug] Heap Size : " - + String(ESP.getHeapSize()) + " B"); - Serial.println("[debug] Available Heap : " - + String(ESP.getFreeHeap()) + " B"); - Serial.println("[debug] Min Free Heap : " - + String(ESP.getMinFreeHeap()) + " B"); - Serial.println("[debug] Max Allocatable : " - + String(ESP.getMaxAllocHeap()) + " B"); +void printHeapUsage() +{ + Serial.println("[debug] Heap Size : " + String(ESP.getHeapSize()) + " B"); + Serial.println("[debug] Available Heap : " + String(ESP.getFreeHeap()) + " B"); + Serial.println("[debug] Min Free Heap : " + String(ESP.getMinFreeHeap()) + " B"); + Serial.println("[debug] Max Allocatable : " + String(ESP.getMaxAllocHeap()) + " B"); return; } - diff --git a/platformio/src/config.cpp b/platformio/src/config.cpp index 16f059522..efe209f59 100644 --- a/platformio/src/config.cpp +++ b/platformio/src/config.cpp @@ -17,49 +17,43 @@ #include #include "config.h" +#include "credentials.h" // PINS -// The configuration below is intended for use with the project's official +// The configuration below is intended for use with the project's official // wiring diagrams using the FireBeetle 2 ESP32-E microcontroller board. // // Note: LED_BUILTIN pin will be disabled to reduce power draw. Refer to your -// board's pinout to ensure you avoid using a pin with this shared +// board's pinout to ensure you avoid using a pin with this shared // functionality. // // ADC pin used to measure battery voltage -const uint8_t PIN_BAT_ADC = A2; // A0 for micro-usb firebeetle +const uint8_t PIN_BAT_ADC = A2; // A0 for micro-usb firebeetle // Pins for E-Paper Driver Board const uint8_t PIN_EPD_BUSY = 14; // 5 for micro-usb firebeetle -const uint8_t PIN_EPD_CS = 13; -const uint8_t PIN_EPD_RST = 21; -const uint8_t PIN_EPD_DC = 22; -const uint8_t PIN_EPD_SCK = 18; +const uint8_t PIN_EPD_CS = 13; +const uint8_t PIN_EPD_RST = 21; +const uint8_t PIN_EPD_DC = 22; +const uint8_t PIN_EPD_SCK = 18; const uint8_t PIN_EPD_MISO = 19; // 19 Master-In Slave-Out not used, as no data from display const uint8_t PIN_EPD_MOSI = 23; -const uint8_t PIN_EPD_PWR = 26; // Irrelevant if directly connected to 3.3V +const uint8_t PIN_EPD_PWR = 26; // Irrelevant if directly connected to 3.3V // I2C Pins used for BME280 const uint8_t PIN_BME_SDA = 17; const uint8_t PIN_BME_SCL = 16; -const uint8_t PIN_BME_PWR = 4; // Irrelevant if directly connected to 3.3V +const uint8_t PIN_BME_PWR = 4; // Irrelevant if directly connected to 3.3V const uint8_t BME_ADDRESS = 0x76; // If sensor does not work, try 0x77 -// WIFI -const char *WIFI_SSID = "ssid"; -const char *WIFI_PASSWORD = "password"; const unsigned long WIFI_TIMEOUT = 10000; // ms, WiFi connection timeout. // HTTP -// The following errors are likely the result of insuffient http client tcp +// The following errors are likely the result of insuffient http client tcp // timeout: // -1 Connection Refused // -11 Read Timeout // -258 Deserialization Incomplete Input const unsigned HTTP_CLIENT_TCP_TIMEOUT = 10000; // ms -// OPENWEATHERMAP API -// OpenWeatherMap API key, https://openweathermap.org/ -const String OWM_APIKEY = "abcdefghijklmnopqrstuvwxyz012345"; -const String OWM_ENDPOINT = "api.openweathermap.org"; // OpenWeatherMap One Call 2.5 API is deprecated for all new free users // (accounts created after Summer 2022). // @@ -76,28 +70,28 @@ const String OWM_ENDPOINT = "api.openweathermap.org"; // calls. const String OWM_ONECALL_VERSION = "3.0"; -// LOCATION -// Set your latitude and longitude. -// (used to get weather data as part of API requests to OpenWeatherMap) -const String LAT = "40.7128"; -const String LON = "-74.0060"; -// City name that will be shown in the top-right corner of the display. -const String CITY_STRING = "New York"; +// OPENWEATHERMAP API +// OpenWeatherMap API key, https://openweathermap.org/ +const String OWM_ENDPOINT = "api.openweathermap.org"; + +// OPENMETEO API +// Open Meteo API, https://open-meteo.com/ +const String OM_ENDPOINT = "api.open-meteo.com"; // TIME // For list of time zones see // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv -const char *TIMEZONE = "EST5EDT,M3.2.0,M11.1.0"; +const char *TIMEZONE = "CET-1CEST,M3.5.0,M10.5.0/3"; // Time format used when displaying sunrise/set times. (Max 11 characters) // For more information about formatting see // https://man7.org/linux/man-pages/man3/strftime.3.html // const char *TIME_FORMAT = "%l:%M%P"; // 12-hour ex: 1:23am 11:00pm -const char *TIME_FORMAT = "%H:%M"; // 24-hour ex: 01:23 23:00 +const char *TIME_FORMAT = "%H:%M"; // 24-hour ex: 01:23 23:00 // Time format used when displaying axis labels. (Max 11 characters) // For more information about formatting see // https://man7.org/linux/man-pages/man3/strftime.3.html // const char *HOUR_FORMAT = "%l%P"; // 12-hour ex: 1am 11pm -const char *HOUR_FORMAT = "%H"; // 24-hour ex: 01 23 +const char *HOUR_FORMAT = "%H"; // 24-hour ex: 01 23 // Date format used when displaying date in top-right corner. // For more information about formatting see // https://man7.org/linux/man-pages/man3/strftime.3.html @@ -124,7 +118,7 @@ const int SLEEP_DURATION = 30; // minutes // Bed Time Power Savings. // If BED_TIME == WAKE_TIME, then this battery saving feature will be disabled. // (range: [0-23]) -const int BED_TIME = 00; // Last update at 00:00 (midnight) until WAKE_TIME. +const int BED_TIME = 00; // Last update at 00:00 (midnight) until WAKE_TIME. const int WAKE_TIME = 06; // Hour of first update after BED_TIME, 06:00. // Note that the minute alignment of SLEEP_DURATION begins at WAKE_TIME even if // Bed Time Power Savings is disabled. @@ -145,11 +139,11 @@ const int HOURLY_GRAPH_MAX = 24; // minutes). Once the battery voltage has fallen to CRIT_LOW_BATTERY_VOLTAGE, // the esp32 will hibernate and a manual press of the reset (RST) button to // begin operating again. -const uint32_t WARN_BATTERY_VOLTAGE = 3535; // (millivolts) ~20% -const uint32_t LOW_BATTERY_VOLTAGE = 3462; // (millivolts) ~10% -const uint32_t VERY_LOW_BATTERY_VOLTAGE = 3442; // (millivolts) ~8% -const uint32_t CRIT_LOW_BATTERY_VOLTAGE = 3404; // (millivolts) ~5% -const unsigned long LOW_BATTERY_SLEEP_INTERVAL = 30; // (minutes) +const uint32_t WARN_BATTERY_VOLTAGE = 3535; // (millivolts) ~20% +const uint32_t LOW_BATTERY_VOLTAGE = 3462; // (millivolts) ~10% +const uint32_t VERY_LOW_BATTERY_VOLTAGE = 3442; // (millivolts) ~8% +const uint32_t CRIT_LOW_BATTERY_VOLTAGE = 3404; // (millivolts) ~5% +const unsigned long LOW_BATTERY_SLEEP_INTERVAL = 30; // (minutes) const unsigned long VERY_LOW_BATTERY_SLEEP_INTERVAL = 120; // (minutes) // Battery voltage calculations are based on a typical 3.7v LiPo. const uint32_t MAX_BATTERY_VOLTAGE = 4200; // (millivolts) @@ -163,4 +157,3 @@ const uint32_t MIN_BATTERY_VOLTAGE = 3000; // (millivolts) // FONTS // ALERTS // BATTERY MONITORING - diff --git a/platformio/src/display_utils.cpp b/platformio/src/display_utils.cpp index b2707e99c..a42c7f70b 100644 --- a/platformio/src/display_utils.cpp +++ b/platformio/src/display_utils.cpp @@ -76,10 +76,10 @@ uint32_t readBatteryVoltage() /* Returns battery percentage, rounded to the nearest integer. * Takes a voltage in millivolts and uses a sigmoidal approximation to find an * approximation of the battery life percentage remaining. - * - * This function contains LGPLv3 code from + * + * This function contains LGPLv3 code from * . - * + * * Symmetric sigmoidal approximation * * @@ -88,13 +88,13 @@ uint32_t readBatteryVoltage() uint32_t calcBatPercent(uint32_t v, uint32_t minv, uint32_t maxv) { // slow - //uint32_t p = 110 - (110 / (1 + pow(1.468 * (v - minv)/(maxv - minv), 6))); + // uint32_t p = 110 - (110 / (1 + pow(1.468 * (v - minv)/(maxv - minv), 6))); // steep - //uint32_t p = 102 - (102 / (1 + pow(1.621 * (v - minv)/(maxv - minv), 8.1))); + // uint32_t p = 102 - (102 / (1 + pow(1.621 * (v - minv)/(maxv - minv), 8.1))); // normal - uint32_t p = 105 - (105 / (1 + pow(1.724 * (v - minv)/(maxv - minv), 5.5))); + uint32_t p = 105 - (105 / (1 + pow(1.724 * (v - minv) / (maxv - minv), 5.5))); return p >= 100 ? 100 : p; } // end calcBatPercent @@ -131,7 +131,7 @@ const uint8_t *getBatBitmap24(uint32_t batPercent) return battery_1_bar_90deg_24x24; } else - { // batPercent < 8 + { // batPercent < 8 return battery_0_bar_90deg_24x24; } } // end getBatBitmap24 @@ -180,9 +180,7 @@ void toTitleCase(String &text) for (int i = 1; i < text.length(); ++i) { - if (text.charAt(i - 1) == ' ' - || text.charAt(i - 1) == '-' - || text.charAt(i - 1) == '(') + if (text.charAt(i - 1) == ' ' || text.charAt(i - 1) == '-' || text.charAt(i - 1) == '(') { text.setCharAt(i, toUpperCase(text.charAt(i))); } @@ -211,10 +209,7 @@ void truncateExtraAlertInfo(String &text) int i = 1; int lastChar = i; - while (i < text.length() - && text.charAt(i) != ',' - && text.charAt(i) != '.' - && text.charAt(i) != '(') + while (i < text.length() && text.charAt(i) != ',' && text.charAt(i) != '.' && text.charAt(i) != '(') { if (text.charAt(i) != ' ') { @@ -390,7 +385,7 @@ const char *getWiFidesc(int rssi) return TXT_WIFI_FAIR; } else - { // rssi < -70 + { // rssi < -70 return TXT_WIFI_WEAK; } } // end getWiFidesc @@ -416,14 +411,14 @@ const uint8_t *getWiFiBitmap16(int rssi) return wifi_2_bar_16x16; } else - { // rssi < -70 + { // rssi < -70 return wifi_1_bar_16x16; } } // end getWiFiBitmap24 /* Returns true if icon is a daytime icon, false otherwise. */ -bool isDay(String icon) +bool isDay(String icon) { // OpenWeatherMap indicates sun is up with d otherwise n for night return icon.endsWith("d"); @@ -437,9 +432,7 @@ bool isMoonInSky(int64_t current_dt, int64_t moonrise_dt, int64_t moonset_dt, // (moon is out if current time is after moonrise but before moonset // OR if moonrises after moonset and the current time is after moonrise) // AND (moon phase is not a new moon) - return ((current_dt >= moonrise_dt && current_dt < moonset_dt) - || (moonrise_dt > moonset_dt && current_dt >= moonrise_dt)) - && (moon_phase != 0.f && moon_phase != 1.f); + return ((current_dt >= moonrise_dt && current_dt < moonset_dt) || (moonrise_dt > moonset_dt && current_dt >= moonrise_dt)) && (moon_phase != 0.f && moon_phase != 1.f); } /* Takes cloudiness (%) and returns true if it is at least partially cloudy, @@ -448,7 +441,8 @@ bool isMoonInSky(int64_t current_dt, int64_t moonrise_dt, int64_t moonset_dt, * References: * https://www.weather.gov/ajk/ForecastTerms */ -bool isCloudy(int clouds) { +bool isCloudy(int clouds) +{ return clouds > 60.25; // partly cloudy / partly sunny } @@ -458,11 +452,13 @@ bool isCloudy(int clouds) { * References: * https://www.weather.gov/ajk/ForecastTerms */ -bool isWindy(float wind_speed, float wind_gust) { - return (wind_speed >= 32.2 /*m/s*/ - || wind_gust >= 40.2 /*m/s*/); +bool isWindy(float wind_speed, float wind_gust) +{ + return (wind_speed >= 32.2 /*m/s*/ + || wind_gust >= 40.2 /*m/s*/); } +#if defined(USE_OPEN_WEATHER_MAP) /* Takes the current weather and today's daily weather forcast (from * OpenWeatherMap API response) and returns a pointer to the icon's 196x196 * bitmap. @@ -489,14 +485,26 @@ const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, case 211: // Thunderstorm thunderstorm 11d case 212: // Thunderstorm heavy thunderstorm 11d case 221: // Thunderstorm ragged thunderstorm 11d - if (!cloudy && day) {return getBitmap(wi_day_thunderstorm, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_thunderstorm, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_thunderstorm, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_thunderstorm, BitmapSize); + } return getBitmap(wi_thunderstorm, BitmapSize); case 230: // Thunderstorm thunderstorm with light drizzle 11d case 231: // Thunderstorm thunderstorm with drizzle 11d case 232: // Thunderstorm thunderstorm with heavy drizzle 11d - if (!cloudy && day) {return getBitmap(wi_day_storm_showers, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_storm_showers, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_storm_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_storm_showers, BitmapSize); + } return getBitmap(wi_storm_showers, BitmapSize); // Group 3xx: Drizzle case 300: // Drizzle light intensity drizzle 09d @@ -508,8 +516,14 @@ const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, case 313: // Drizzle shower rain and drizzle 09d case 314: // Drizzle heavy shower rain and drizzle 09d case 321: // Drizzle shower drizzle 09d - if (!cloudy && day) {return getBitmap(wi_day_showers, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_showers, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_showers, BitmapSize); + } return getBitmap(wi_showers, BitmapSize); // Group 5xx: Rain case 500: // Rain light rain 10d @@ -517,62 +531,131 @@ const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, case 502: // Rain heavy intensity rain 10d case 503: // Rain very heavy rain 10d case 504: // Rain extreme rain 10d - if (!cloudy && day && windy) {return getBitmap(wi_day_rain_wind, BitmapSize);} - if (!cloudy && day) {return getBitmap(wi_day_rain, BitmapSize);} - if (!cloudy && !day && moon && windy) {return getBitmap(wi_night_alt_rain_wind, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_rain, BitmapSize);} - if (windy) {return getBitmap(wi_rain_wind, BitmapSize);} + if (!cloudy && day && windy) + { + return getBitmap(wi_day_rain_wind, BitmapSize); + } + if (!cloudy && day) + { + return getBitmap(wi_day_rain, BitmapSize); + } + if (!cloudy && !day && moon && windy) + { + return getBitmap(wi_night_alt_rain_wind, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain, BitmapSize); + } + if (windy) + { + return getBitmap(wi_rain_wind, BitmapSize); + } return getBitmap(wi_rain, BitmapSize); case 511: // Rain freezing rain 13d - if (!cloudy && day) {return getBitmap(wi_day_rain_mix, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_rain_mix, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_rain_mix, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain_mix, BitmapSize); + } return getBitmap(wi_rain_mix, BitmapSize); case 520: // Rain light intensity shower rain 09d case 521: // Rain shower rain 09d case 522: // Rain heavy intensity shower rain 09d case 531: // Rain ragged shower rain 09d - if (!cloudy && day) {return getBitmap(wi_day_showers, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_showers, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_showers, BitmapSize); + } return getBitmap(wi_showers, BitmapSize); // Group 6xx: Snow case 600: // Snow light snow 13d case 601: // Snow Snow 13d case 602: // Snow Heavy snow 13d - if (!cloudy && day && windy) {return getBitmap(wi_day_snow_wind, BitmapSize);} - if (!cloudy && day) {return getBitmap(wi_day_snow, BitmapSize);} - if (!cloudy && !day && moon && windy) {return getBitmap(wi_night_alt_snow_wind, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_snow, BitmapSize);} - if (windy) {return getBitmap(wi_snow_wind, BitmapSize);} + if (!cloudy && day && windy) + { + return getBitmap(wi_day_snow_wind, BitmapSize); + } + if (!cloudy && day) + { + return getBitmap(wi_day_snow, BitmapSize); + } + if (!cloudy && !day && moon && windy) + { + return getBitmap(wi_night_alt_snow_wind, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_snow, BitmapSize); + } + if (windy) + { + return getBitmap(wi_snow_wind, BitmapSize); + } return getBitmap(wi_snow, BitmapSize); case 611: // Snow Sleet 13d case 612: // Snow Light shower sleet 13d case 613: // Snow Shower sleet 13d - if (!cloudy && day) {return getBitmap(wi_day_sleet, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_sleet, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_sleet, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_sleet, BitmapSize); + } return getBitmap(wi_sleet, BitmapSize); case 615: // Snow Light rain and snow 13d case 616: // Snow Rain and snow 13d case 620: // Snow Light shower snow 13d case 621: // Snow Shower snow 13d case 622: // Snow Heavy shower snow 13d - if (!cloudy && day) {return getBitmap(wi_day_rain_mix, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_alt_rain_mix, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_rain_mix, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain_mix, BitmapSize); + } return getBitmap(wi_rain_mix, BitmapSize); // Group 7xx: Atmosphere case 701: // Mist mist 50d - if (!cloudy && day) {return getBitmap(wi_day_fog, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_fog, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_fog, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_fog, BitmapSize); + } return getBitmap(wi_fog, BitmapSize); case 711: // Smoke Smoke 50d return getBitmap(wi_smoke, BitmapSize); case 721: // Haze Haze 50d - if (day && !cloudy) {return getBitmap(wi_day_haze, BitmapSize);} + if (day && !cloudy) + { + return getBitmap(wi_day_haze, BitmapSize); + } return getBitmap(wi_dust, BitmapSize); case 731: // Dust sand/dust whirls 50d return getBitmap(wi_sandstorm, BitmapSize); case 741: // Fog fog 50d - if (!cloudy && day) {return getBitmap(wi_day_fog, BitmapSize);} - if (!cloudy && !day && moon) {return getBitmap(wi_night_fog, BitmapSize);} + if (!cloudy && day) + { + return getBitmap(wi_day_fog, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_fog, BitmapSize); + } return getBitmap(wi_fog, BitmapSize); case 751: // Sand sand 50d return getBitmap(wi_sandstorm, BitmapSize); @@ -586,38 +669,321 @@ const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, return getBitmap(wi_tornado, BitmapSize); // Group 800: Clear case 800: // Clear clear sky 01d 01n - if (windy) {return getBitmap(wi_strong_wind, BitmapSize);} - if (!day && moon) {return getBitmap(wi_night_clear, BitmapSize);} - if (!day && !moon) {return getBitmap(wi_stars, BitmapSize);} + if (windy) + { + return getBitmap(wi_strong_wind, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_clear, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_stars, BitmapSize); + } return getBitmap(wi_day_sunny, BitmapSize); // Group 80x: Clouds case 801: // Clouds few clouds: 11-25% 02d 02n - if (windy) {return getBitmap(wi_strong_wind, BitmapSize);} - if (!day && moon) {return getBitmap(wi_night_alt_partly_cloudy, BitmapSize);} - if (!day && !moon) {return getBitmap(wi_stars, BitmapSize);} + if (windy) + { + return getBitmap(wi_strong_wind, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_alt_partly_cloudy, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_stars, BitmapSize); + } return getBitmap(wi_day_sunny_overcast, BitmapSize); case 802: // Clouds scattered clouds: 25-50% 03d 03n case 803: // Clouds broken clouds: 51-84% 04d 04n - if (windy && day) {return getBitmap(wi_day_cloudy_gusts, BitmapSize);} - if (windy && !day && moon) {return getBitmap(wi_night_alt_cloudy_gusts, BitmapSize);} - if (windy && !day && !moon) {return getBitmap(wi_cloudy_gusts, BitmapSize);} - if (!day && moon) {return getBitmap(wi_night_alt_cloudy, BitmapSize);} - if (!day && !moon) {return getBitmap(wi_cloud, BitmapSize);} + if (windy && day) + { + return getBitmap(wi_day_cloudy_gusts, BitmapSize); + } + if (windy && !day && moon) + { + return getBitmap(wi_night_alt_cloudy_gusts, BitmapSize); + } + if (windy && !day && !moon) + { + return getBitmap(wi_cloudy_gusts, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_alt_cloudy, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_cloud, BitmapSize); + } return getBitmap(wi_day_cloudy, BitmapSize); case 804: // Clouds overcast clouds: 85-100% 04d 04n - if (windy) {return getBitmap(wi_cloudy_gusts, BitmapSize);} + if (windy) + { + return getBitmap(wi_cloudy_gusts, BitmapSize); + } return getBitmap(wi_cloudy, BitmapSize); default: // maybe this is a new getBitmap in one of the existing groups - if (id >= 200 && id < 300) {return getBitmap(wi_thunderstorm, BitmapSize);} - if (id >= 300 && id < 400) {return getBitmap(wi_showers, BitmapSize);} - if (id >= 500 && id < 600) {return getBitmap(wi_rain, BitmapSize);} - if (id >= 600 && id < 700) {return getBitmap(wi_snow, BitmapSize);} - if (id >= 700 && id < 800) {return getBitmap(wi_fog, BitmapSize);} - if (id >= 800 && id < 900) {return getBitmap(wi_cloudy, BitmapSize);} + if (id >= 200 && id < 300) + { + return getBitmap(wi_thunderstorm, BitmapSize); + } + if (id >= 300 && id < 400) + { + return getBitmap(wi_showers, BitmapSize); + } + if (id >= 500 && id < 600) + { + return getBitmap(wi_rain, BitmapSize); + } + if (id >= 600 && id < 700) + { + return getBitmap(wi_snow, BitmapSize); + } + if (id >= 700 && id < 800) + { + return getBitmap(wi_fog, BitmapSize); + } + if (id >= 800 && id < 900) + { + return getBitmap(wi_cloudy, BitmapSize); + } + return getBitmap(wi_na, BitmapSize); + } +} // end getConditionsBitmap + +#endif // USE_OPEN_WEATHER_MAP + +#ifdef USE_OPEN_METEO +/* Takes the current weather and today's daily weather forcast (from + * OpenMeteo API response) and returns a pointer to the icon's 196x196 + * bitmap. + * + * Uses multiple factors to return more detailed icons than the simple icon + * catagories that OpenMeteo provides. + * + * Last Updated: June 13, 2025 + */ +template +const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, + bool windy) +{ + switch (id) + { + case 95: // Thunderstorm thunderstorm with light rain 11d + if (!cloudy && day) + { + return getBitmap(wi_day_thunderstorm, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_thunderstorm, BitmapSize); + } + return getBitmap(wi_thunderstorm, BitmapSize); + case 96: // Thunderstorm thunderstorm with heavy drizzle 11d + if (!cloudy && day) + { + return getBitmap(wi_day_storm_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_storm_showers, BitmapSize); + } + return getBitmap(wi_storm_showers, BitmapSize); + case 51: // Drizzle light intensity drizzle 09d + case 53: // Drizzle drizzle 09d + case 55: // Drizzle heavy intensity drizzle 09d + case 56: // Drizzle heavy shower rain and drizzle 09d + case 57: // Drizzle shower drizzle 09d + if (!cloudy && day) + { + return getBitmap(wi_day_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_showers, BitmapSize); + } + return getBitmap(wi_showers, BitmapSize); + case 61: // Rain light rain 10d + case 62: // Rain moderate rain 10d + case 63: // Rain heavy intensity rain 10d + if (!cloudy && day && windy) + { + return getBitmap(wi_day_rain_wind, BitmapSize); + } + if (!cloudy && day) + { + return getBitmap(wi_day_rain, BitmapSize); + } + if (!cloudy && !day && moon && windy) + { + return getBitmap(wi_night_alt_rain_wind, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain, BitmapSize); + } + if (windy) + { + return getBitmap(wi_rain_wind, BitmapSize); + } + return getBitmap(wi_rain, BitmapSize); + case 66: // Rain freezing rain 13d + case 67: // Rain freezing rain 13d if (!cloudy) {return wi_day_rain_mix_64x64;} + if (!cloudy && day) + { + return getBitmap(wi_day_rain_mix, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain_mix, BitmapSize); + } + return getBitmap(wi_rain_mix, BitmapSize); + case 80: // Rain light intensity shower rain 09d + case 81: // Rain shower rain 09d + case 82: // Rain heavy intensity shower rain 09d + if (!cloudy && day) + { + return getBitmap(wi_day_showers, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_showers, BitmapSize); + } + return getBitmap(wi_showers, BitmapSize); + case 71: // Snow light snow 13d + case 73: // Snow Snow 13d + case 75: // Snow Heavy snow 13d + if (!cloudy && day && windy) + { + return getBitmap(wi_day_snow_wind, BitmapSize); + } + if (!cloudy && day) + { + return getBitmap(wi_day_snow, BitmapSize); + } + if (!cloudy && !day && moon && windy) + { + return getBitmap(wi_night_alt_snow_wind, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_snow, BitmapSize); + } + if (windy) + { + return getBitmap(wi_snow_wind, BitmapSize); + } + return getBitmap(wi_snow, BitmapSize); + case 77: // Snow light snow 13d + if (!cloudy && day) + { + return getBitmap(wi_day_sleet, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_sleet, BitmapSize); + } + return getBitmap(wi_sleet, BitmapSize); + case 85: // Snow Light rain and snow 13d + case 86: // Snow Rain and snow 13d + if (!cloudy && day) + { + return getBitmap(wi_day_rain_mix, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_alt_rain_mix, BitmapSize); + } + return getBitmap(wi_rain_mix, BitmapSize); + // Group 7xx: Atmosphere + case 48: // Mist mist 50d + if (!cloudy && day) + { + return getBitmap(wi_day_fog, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_fog, BitmapSize); + } + return getBitmap(wi_fog, BitmapSize); + case 45: // Fog fog 50d + if (!cloudy && day) + { + return getBitmap(wi_day_fog, BitmapSize); + } + if (!cloudy && !day && moon) + { + return getBitmap(wi_night_fog, BitmapSize); + } + return getBitmap(wi_fog, BitmapSize); + // Group 800: Clear + case 0: // Clear clear sky 01d 01n + if (windy) + { + return getBitmap(wi_strong_wind, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_clear, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_stars, BitmapSize); + } + return getBitmap(wi_day_sunny, BitmapSize); + // Group 80x: Clouds + case 1: // Clouds few clouds: 11-25% 02d 02n + if (windy) + { + return getBitmap(wi_strong_wind, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_alt_partly_cloudy, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_stars, BitmapSize); + } + return getBitmap(wi_day_sunny_overcast, BitmapSize); + case 2: // Clouds scattered clouds: 25-50% 03d 03n + if (windy && day) + { + return getBitmap(wi_day_cloudy_gusts, BitmapSize); + } + if (windy && !day && moon) + { + return getBitmap(wi_night_alt_cloudy_gusts, BitmapSize); + } + if (windy && !day && !moon) + { + return getBitmap(wi_cloudy_gusts, BitmapSize); + } + if (!day && moon) + { + return getBitmap(wi_night_alt_cloudy, BitmapSize); + } + if (!day && !moon) + { + return getBitmap(wi_cloud, BitmapSize); + } + return getBitmap(wi_day_cloudy, BitmapSize); + case 3: // Clouds overcast clouds: 85-100% 04d 04n + if (windy) + { + return getBitmap(wi_cloudy_gusts, BitmapSize); + } + return getBitmap(wi_cloudy, BitmapSize); + default: return getBitmap(wi_na, BitmapSize); } } // end getConditionsBitmap +#endif // USE_OPEN_METEO /* Takes the daily weather forecast (from OpenWeatherMap API response) and * returns a pointer to the icon's 32x32 bitmap. @@ -625,7 +991,7 @@ const uint8_t *getConditionsBitmap(int id, bool day, bool moon, bool cloudy, * The daily weather forcast of today is needed for moonrise and moonset times. */ const uint8_t *getHourlyForecastBitmap32(const owm_hourly_t &hourly, - const owm_daily_t &today) + const owm_daily_t &today) { const int id = hourly.weather.id; const bool day = isDay(hourly.weather.icon); @@ -648,16 +1014,16 @@ const uint8_t *getDailyForecastBitmap64(const owm_daily_t &daily) const bool cloudy = isCloudy(daily.clouds); const bool windy = isWindy(daily.wind_speed, daily.wind_gust); return getConditionsBitmap<64>(id, day, moon, cloudy, windy); -} // end getForecastBitmap64 +} // end getDailyForecastBitmap64 /* Takes the current weather and today's daily weather forcast (from * OpenWeatherMap API response) and returns a pointer to the icon's 196x196 * bitmap. - * + * * The daily weather forcast of today is needed for moonrise and moonset times. */ const uint8_t *getCurrentConditionsBitmap196(const owm_current_t ¤t, - const owm_daily_t &today) + const owm_daily_t &today) { const int id = current.weather.id; const bool day = isDay(current.weather.icon); @@ -682,35 +1048,61 @@ const uint8_t *getAlertBitmap32(const owm_alerts_t &alert) switch (c) { // this is the default if an alert wasn't associated with a catagory - case NOT_FOUND: return warning_icon_32x32; - - case SMOG: return wi_smog_32x32; - case SMOKE: return wi_smoke_32x32; - case FOG: return wi_fog_32x32; - case METEOR: return wi_meteor_32x32; - case NUCLEAR: return ionizing_radiation_symbol_32x32; - case BIOHAZARD: return biological_hazard_symbol_32x32; - case EARTHQUAKE: return wi_earthquake_32x32; - case TSUNAMI: return wi_tsunami_32x32; - case FIRE: return wi_fire_32x32; - case HEAT: return wi_thermometer_32x32; - case WINTER: return wi_snowflake_cold_32x32; - case LIGHTNING: return wi_lightning_32x32; - case SANDSTORM: return wi_sandstorm_32x32; - case FLOOD: return wi_flood_32x32; - case VOLCANO: return wi_volcano_32x32; - case AIR_QUALITY: return wi_dust_32x32; - case TORNADO: return wi_tornado_32x32; - case SMALL_CRAFT_ADVISORY: return wi_small_craft_advisory_32x32; - case GALE_WARNING: return wi_gale_warning_32x32; - case STORM_WARNING: return wi_storm_warning_32x32; - case HURRICANE_WARNING: return wi_hurricane_warning_32x32; - case HURRICANE: return wi_hurricane_32x32; - case DUST: return wi_dust_32x32; - case STRONG_WIND: return wi_strong_wind_32x32; + case NOT_FOUND: + return warning_icon_32x32; + + case SMOG: + return wi_smog_32x32; + case SMOKE: + return wi_smoke_32x32; + case FOG: + return wi_fog_32x32; + case METEOR: + return wi_meteor_32x32; + case NUCLEAR: + return ionizing_radiation_symbol_32x32; + case BIOHAZARD: + return biological_hazard_symbol_32x32; + case EARTHQUAKE: + return wi_earthquake_32x32; + case TSUNAMI: + return wi_tsunami_32x32; + case FIRE: + return wi_fire_32x32; + case HEAT: + return wi_thermometer_32x32; + case WINTER: + return wi_snowflake_cold_32x32; + case LIGHTNING: + return wi_lightning_32x32; + case SANDSTORM: + return wi_sandstorm_32x32; + case FLOOD: + return wi_flood_32x32; + case VOLCANO: + return wi_volcano_32x32; + case AIR_QUALITY: + return wi_dust_32x32; + case TORNADO: + return wi_tornado_32x32; + case SMALL_CRAFT_ADVISORY: + return wi_small_craft_advisory_32x32; + case GALE_WARNING: + return wi_gale_warning_32x32; + case STORM_WARNING: + return wi_storm_warning_32x32; + case HURRICANE_WARNING: + return wi_hurricane_warning_32x32; + case HURRICANE: + return wi_hurricane_32x32; + case DUST: + return wi_dust_32x32; + case STRONG_WIND: + return wi_strong_wind_32x32; // this code will never be reached - default: return wi_na_48x48; + default: + return wi_na_48x48; } } // end getAlertBitmap32 @@ -728,35 +1120,61 @@ const uint8_t *getAlertBitmap48(const owm_alerts_t &alert) switch (c) { // this is the default if an alert wasn't associated with a catagory - case NOT_FOUND: return warning_icon_48x48; - - case SMOG: return wi_smog_48x48; - case SMOKE: return wi_smoke_48x48; - case FOG: return wi_fog_48x48; - case METEOR: return wi_meteor_48x48; - case NUCLEAR: return ionizing_radiation_symbol_48x48; - case BIOHAZARD: return biological_hazard_symbol_48x48; - case EARTHQUAKE: return wi_earthquake_48x48; - case TSUNAMI: return wi_tsunami_48x48; - case FIRE: return wi_fire_48x48; - case HEAT: return wi_thermometer_48x48; - case WINTER: return wi_snowflake_cold_48x48; - case LIGHTNING: return wi_lightning_48x48; - case SANDSTORM: return wi_sandstorm_48x48; - case FLOOD: return wi_flood_48x48; - case VOLCANO: return wi_volcano_48x48; - case AIR_QUALITY: return wi_dust_48x48; - case TORNADO: return wi_tornado_48x48; - case SMALL_CRAFT_ADVISORY: return wi_small_craft_advisory_48x48; - case GALE_WARNING: return wi_gale_warning_48x48; - case STORM_WARNING: return wi_storm_warning_48x48; - case HURRICANE_WARNING: return wi_hurricane_warning_48x48; - case HURRICANE: return wi_hurricane_48x48; - case DUST: return wi_dust_48x48; - case STRONG_WIND: return wi_strong_wind_48x48; + case NOT_FOUND: + return warning_icon_48x48; + + case SMOG: + return wi_smog_48x48; + case SMOKE: + return wi_smoke_48x48; + case FOG: + return wi_fog_48x48; + case METEOR: + return wi_meteor_48x48; + case NUCLEAR: + return ionizing_radiation_symbol_48x48; + case BIOHAZARD: + return biological_hazard_symbol_48x48; + case EARTHQUAKE: + return wi_earthquake_48x48; + case TSUNAMI: + return wi_tsunami_48x48; + case FIRE: + return wi_fire_48x48; + case HEAT: + return wi_thermometer_48x48; + case WINTER: + return wi_snowflake_cold_48x48; + case LIGHTNING: + return wi_lightning_48x48; + case SANDSTORM: + return wi_sandstorm_48x48; + case FLOOD: + return wi_flood_48x48; + case VOLCANO: + return wi_volcano_48x48; + case AIR_QUALITY: + return wi_dust_48x48; + case TORNADO: + return wi_tornado_48x48; + case SMALL_CRAFT_ADVISORY: + return wi_small_craft_advisory_48x48; + case GALE_WARNING: + return wi_gale_warning_48x48; + case STORM_WARNING: + return wi_storm_warning_48x48; + case HURRICANE_WARNING: + return wi_hurricane_warning_48x48; + case HURRICANE: + return wi_hurricane_48x48; + case DUST: + return wi_dust_48x48; + case STRONG_WIND: + return wi_strong_wind_48x48; // this code will never be reached - default: return wi_na_48x48; + default: + return wi_na_48x48; } } // end getAlertBitmap48 @@ -885,438 +1303,438 @@ enum alert_category getAlertCategory(const owm_alerts_t &alert) #ifdef WIND_ICONS_CARDINAL static const unsigned char *wind_direction_icon_arr[] = { - wind_direction_meteorological_0deg_24x24, // N - wind_direction_meteorological_90deg_24x24, // E - wind_direction_meteorological_180deg_24x24, // S - wind_direction_meteorological_270deg_24x24}; // W -#endif // end WIND_ICONS_CARDINAL + wind_direction_meteorological_0deg_24x24, // N + wind_direction_meteorological_90deg_24x24, // E + wind_direction_meteorological_180deg_24x24, // S + wind_direction_meteorological_270deg_24x24}; // W +#endif // end WIND_ICONS_CARDINAL #ifdef WIND_ICONS_INTERCARDINAL static const unsigned char *wind_direction_icon_arr[] = { - wind_direction_meteorological_0deg_24x24, // N - wind_direction_meteorological_45deg_24x24, // NE - wind_direction_meteorological_90deg_24x24, // E - wind_direction_meteorological_135deg_24x24, // SE - wind_direction_meteorological_180deg_24x24, // S - wind_direction_meteorological_225deg_24x24, // SW - wind_direction_meteorological_270deg_24x24, // W - wind_direction_meteorological_315deg_24x24}; // NW -#endif // end WIND_ICONS_INTERCARDINAL + wind_direction_meteorological_0deg_24x24, // N + wind_direction_meteorological_45deg_24x24, // NE + wind_direction_meteorological_90deg_24x24, // E + wind_direction_meteorological_135deg_24x24, // SE + wind_direction_meteorological_180deg_24x24, // S + wind_direction_meteorological_225deg_24x24, // SW + wind_direction_meteorological_270deg_24x24, // W + wind_direction_meteorological_315deg_24x24}; // NW +#endif // end WIND_ICONS_INTERCARDINAL #ifdef WIND_ICONS_SECONDARY_INTERCARDINAL static const unsigned char *wind_direction_icon_arr[] = { - wind_direction_meteorological_0deg_24x24, // N - wind_direction_meteorological_22_5deg_24x24, // NNE - wind_direction_meteorological_45deg_24x24, // NE - wind_direction_meteorological_67_5deg_24x24, // ENE - wind_direction_meteorological_90deg_24x24, // E - wind_direction_meteorological_112_5deg_24x24, // ESE - wind_direction_meteorological_135deg_24x24, // SE - wind_direction_meteorological_157_5deg_24x24, // SSE - wind_direction_meteorological_180deg_24x24, // S - wind_direction_meteorological_202_5deg_24x24, // SSW - wind_direction_meteorological_225deg_24x24, // SW - wind_direction_meteorological_247_5deg_24x24, // WSW - wind_direction_meteorological_270deg_24x24, // W - wind_direction_meteorological_292_5deg_24x24, // WNW - wind_direction_meteorological_315deg_24x24, // NW - wind_direction_meteorological_337_5deg_24x24}; // NNW -#endif // end WIND_ICONS_SECONDARY_INTERCARDINAL + wind_direction_meteorological_0deg_24x24, // N + wind_direction_meteorological_22_5deg_24x24, // NNE + wind_direction_meteorological_45deg_24x24, // NE + wind_direction_meteorological_67_5deg_24x24, // ENE + wind_direction_meteorological_90deg_24x24, // E + wind_direction_meteorological_112_5deg_24x24, // ESE + wind_direction_meteorological_135deg_24x24, // SE + wind_direction_meteorological_157_5deg_24x24, // SSE + wind_direction_meteorological_180deg_24x24, // S + wind_direction_meteorological_202_5deg_24x24, // SSW + wind_direction_meteorological_225deg_24x24, // SW + wind_direction_meteorological_247_5deg_24x24, // WSW + wind_direction_meteorological_270deg_24x24, // W + wind_direction_meteorological_292_5deg_24x24, // WNW + wind_direction_meteorological_315deg_24x24, // NW + wind_direction_meteorological_337_5deg_24x24}; // NNW +#endif // end WIND_ICONS_SECONDARY_INTERCARDINAL #ifdef WIND_ICONS_TERTIARY_INTERCARDINAL static const unsigned char *wind_direction_icon_arr[] = { - wind_direction_meteorological_0deg_24x24, // N - wind_direction_meteorological_11_25deg_24x24, // NbE - wind_direction_meteorological_22_5deg_24x24, // NNE - wind_direction_meteorological_33_75deg_24x24, // NEbN - wind_direction_meteorological_45deg_24x24, // NE - wind_direction_meteorological_56_25deg_24x24, // NEbE - wind_direction_meteorological_67_5deg_24x24, // ENE - wind_direction_meteorological_78_75deg_24x24, // EbN - wind_direction_meteorological_90deg_24x24, // E - wind_direction_meteorological_101_25deg_24x24, // EbS - wind_direction_meteorological_112_5deg_24x24, // ESE - wind_direction_meteorological_123_75deg_24x24, // SEbE - wind_direction_meteorological_135deg_24x24, // SE - wind_direction_meteorological_146_25deg_24x24, // SEbS - wind_direction_meteorological_157_5deg_24x24, // SSE - wind_direction_meteorological_168_75deg_24x24, // SbE - wind_direction_meteorological_180deg_24x24, // S - wind_direction_meteorological_191_25deg_24x24, // SbW - wind_direction_meteorological_202_5deg_24x24, // SSW - wind_direction_meteorological_213_75deg_24x24, // SWbS - wind_direction_meteorological_225deg_24x24, // SW - wind_direction_meteorological_236_25deg_24x24, // SWbW - wind_direction_meteorological_247_5deg_24x24, // WSW - wind_direction_meteorological_258_75deg_24x24, // WbS - wind_direction_meteorological_270deg_24x24, // W - wind_direction_meteorological_281_25deg_24x24, // WbN - wind_direction_meteorological_292_5deg_24x24, // WNW - wind_direction_meteorological_303_75deg_24x24, // NWbW - wind_direction_meteorological_315deg_24x24, // NW - wind_direction_meteorological_326_25deg_24x24, // NWbN - wind_direction_meteorological_337_5deg_24x24, // NNW - wind_direction_meteorological_348_75deg_24x24}; // NbW -#endif // end WIND_ICONS_TERTIARY_INTERCARDINAL + wind_direction_meteorological_0deg_24x24, // N + wind_direction_meteorological_11_25deg_24x24, // NbE + wind_direction_meteorological_22_5deg_24x24, // NNE + wind_direction_meteorological_33_75deg_24x24, // NEbN + wind_direction_meteorological_45deg_24x24, // NE + wind_direction_meteorological_56_25deg_24x24, // NEbE + wind_direction_meteorological_67_5deg_24x24, // ENE + wind_direction_meteorological_78_75deg_24x24, // EbN + wind_direction_meteorological_90deg_24x24, // E + wind_direction_meteorological_101_25deg_24x24, // EbS + wind_direction_meteorological_112_5deg_24x24, // ESE + wind_direction_meteorological_123_75deg_24x24, // SEbE + wind_direction_meteorological_135deg_24x24, // SE + wind_direction_meteorological_146_25deg_24x24, // SEbS + wind_direction_meteorological_157_5deg_24x24, // SSE + wind_direction_meteorological_168_75deg_24x24, // SbE + wind_direction_meteorological_180deg_24x24, // S + wind_direction_meteorological_191_25deg_24x24, // SbW + wind_direction_meteorological_202_5deg_24x24, // SSW + wind_direction_meteorological_213_75deg_24x24, // SWbS + wind_direction_meteorological_225deg_24x24, // SW + wind_direction_meteorological_236_25deg_24x24, // SWbW + wind_direction_meteorological_247_5deg_24x24, // WSW + wind_direction_meteorological_258_75deg_24x24, // WbS + wind_direction_meteorological_270deg_24x24, // W + wind_direction_meteorological_281_25deg_24x24, // WbN + wind_direction_meteorological_292_5deg_24x24, // WNW + wind_direction_meteorological_303_75deg_24x24, // NWbW + wind_direction_meteorological_315deg_24x24, // NW + wind_direction_meteorological_326_25deg_24x24, // NWbN + wind_direction_meteorological_337_5deg_24x24, // NNW + wind_direction_meteorological_348_75deg_24x24}; // NbW +#endif // end WIND_ICONS_TERTIARY_INTERCARDINAL #ifdef WIND_ICONS_360 static const unsigned char *wind_direction_icon_arr[] = { - wind_direction_meteorological_0deg_24x24, - wind_direction_meteorological_1deg_24x24, - wind_direction_meteorological_2deg_24x24, - wind_direction_meteorological_3deg_24x24, - wind_direction_meteorological_4deg_24x24, - wind_direction_meteorological_5deg_24x24, - wind_direction_meteorological_6deg_24x24, - wind_direction_meteorological_7deg_24x24, - wind_direction_meteorological_8deg_24x24, - wind_direction_meteorological_9deg_24x24, - wind_direction_meteorological_10deg_24x24, - wind_direction_meteorological_11deg_24x24, - wind_direction_meteorological_12deg_24x24, - wind_direction_meteorological_13deg_24x24, - wind_direction_meteorological_14deg_24x24, - wind_direction_meteorological_15deg_24x24, - wind_direction_meteorological_16deg_24x24, - wind_direction_meteorological_17deg_24x24, - wind_direction_meteorological_18deg_24x24, - wind_direction_meteorological_19deg_24x24, - wind_direction_meteorological_20deg_24x24, - wind_direction_meteorological_21deg_24x24, - wind_direction_meteorological_22deg_24x24, - wind_direction_meteorological_23deg_24x24, - wind_direction_meteorological_24deg_24x24, - wind_direction_meteorological_25deg_24x24, - wind_direction_meteorological_26deg_24x24, - wind_direction_meteorological_27deg_24x24, - wind_direction_meteorological_28deg_24x24, - wind_direction_meteorological_29deg_24x24, - wind_direction_meteorological_30deg_24x24, - wind_direction_meteorological_31deg_24x24, - wind_direction_meteorological_32deg_24x24, - wind_direction_meteorological_33deg_24x24, - wind_direction_meteorological_34deg_24x24, - wind_direction_meteorological_35deg_24x24, - wind_direction_meteorological_36deg_24x24, - wind_direction_meteorological_37deg_24x24, - wind_direction_meteorological_38deg_24x24, - wind_direction_meteorological_39deg_24x24, - wind_direction_meteorological_40deg_24x24, - wind_direction_meteorological_41deg_24x24, - wind_direction_meteorological_42deg_24x24, - wind_direction_meteorological_43deg_24x24, - wind_direction_meteorological_44deg_24x24, - wind_direction_meteorological_45deg_24x24, - wind_direction_meteorological_46deg_24x24, - wind_direction_meteorological_47deg_24x24, - wind_direction_meteorological_48deg_24x24, - wind_direction_meteorological_49deg_24x24, - wind_direction_meteorological_50deg_24x24, - wind_direction_meteorological_51deg_24x24, - wind_direction_meteorological_52deg_24x24, - wind_direction_meteorological_53deg_24x24, - wind_direction_meteorological_54deg_24x24, - wind_direction_meteorological_55deg_24x24, - wind_direction_meteorological_56deg_24x24, - wind_direction_meteorological_57deg_24x24, - wind_direction_meteorological_58deg_24x24, - wind_direction_meteorological_59deg_24x24, - wind_direction_meteorological_60deg_24x24, - wind_direction_meteorological_61deg_24x24, - wind_direction_meteorological_62deg_24x24, - wind_direction_meteorological_63deg_24x24, - wind_direction_meteorological_64deg_24x24, - wind_direction_meteorological_65deg_24x24, - wind_direction_meteorological_66deg_24x24, - wind_direction_meteorological_67deg_24x24, - wind_direction_meteorological_68deg_24x24, - wind_direction_meteorological_69deg_24x24, - wind_direction_meteorological_70deg_24x24, - wind_direction_meteorological_71deg_24x24, - wind_direction_meteorological_72deg_24x24, - wind_direction_meteorological_73deg_24x24, - wind_direction_meteorological_74deg_24x24, - wind_direction_meteorological_75deg_24x24, - wind_direction_meteorological_76deg_24x24, - wind_direction_meteorological_77deg_24x24, - wind_direction_meteorological_78deg_24x24, - wind_direction_meteorological_79deg_24x24, - wind_direction_meteorological_80deg_24x24, - wind_direction_meteorological_81deg_24x24, - wind_direction_meteorological_82deg_24x24, - wind_direction_meteorological_83deg_24x24, - wind_direction_meteorological_84deg_24x24, - wind_direction_meteorological_85deg_24x24, - wind_direction_meteorological_86deg_24x24, - wind_direction_meteorological_87deg_24x24, - wind_direction_meteorological_88deg_24x24, - wind_direction_meteorological_89deg_24x24, - wind_direction_meteorological_90deg_24x24, - wind_direction_meteorological_91deg_24x24, - wind_direction_meteorological_92deg_24x24, - wind_direction_meteorological_93deg_24x24, - wind_direction_meteorological_94deg_24x24, - wind_direction_meteorological_95deg_24x24, - wind_direction_meteorological_96deg_24x24, - wind_direction_meteorological_97deg_24x24, - wind_direction_meteorological_98deg_24x24, - wind_direction_meteorological_99deg_24x24, - wind_direction_meteorological_100deg_24x24, - wind_direction_meteorological_101deg_24x24, - wind_direction_meteorological_102deg_24x24, - wind_direction_meteorological_103deg_24x24, - wind_direction_meteorological_104deg_24x24, - wind_direction_meteorological_105deg_24x24, - wind_direction_meteorological_106deg_24x24, - wind_direction_meteorological_107deg_24x24, - wind_direction_meteorological_108deg_24x24, - wind_direction_meteorological_109deg_24x24, - wind_direction_meteorological_110deg_24x24, - wind_direction_meteorological_111deg_24x24, - wind_direction_meteorological_112deg_24x24, - wind_direction_meteorological_113deg_24x24, - wind_direction_meteorological_114deg_24x24, - wind_direction_meteorological_115deg_24x24, - wind_direction_meteorological_116deg_24x24, - wind_direction_meteorological_117deg_24x24, - wind_direction_meteorological_118deg_24x24, - wind_direction_meteorological_119deg_24x24, - wind_direction_meteorological_120deg_24x24, - wind_direction_meteorological_121deg_24x24, - wind_direction_meteorological_122deg_24x24, - wind_direction_meteorological_123deg_24x24, - wind_direction_meteorological_124deg_24x24, - wind_direction_meteorological_125deg_24x24, - wind_direction_meteorological_126deg_24x24, - wind_direction_meteorological_127deg_24x24, - wind_direction_meteorological_128deg_24x24, - wind_direction_meteorological_129deg_24x24, - wind_direction_meteorological_130deg_24x24, - wind_direction_meteorological_131deg_24x24, - wind_direction_meteorological_132deg_24x24, - wind_direction_meteorological_133deg_24x24, - wind_direction_meteorological_134deg_24x24, - wind_direction_meteorological_135deg_24x24, - wind_direction_meteorological_136deg_24x24, - wind_direction_meteorological_137deg_24x24, - wind_direction_meteorological_138deg_24x24, - wind_direction_meteorological_139deg_24x24, - wind_direction_meteorological_140deg_24x24, - wind_direction_meteorological_141deg_24x24, - wind_direction_meteorological_142deg_24x24, - wind_direction_meteorological_143deg_24x24, - wind_direction_meteorological_144deg_24x24, - wind_direction_meteorological_145deg_24x24, - wind_direction_meteorological_146deg_24x24, - wind_direction_meteorological_147deg_24x24, - wind_direction_meteorological_148deg_24x24, - wind_direction_meteorological_149deg_24x24, - wind_direction_meteorological_150deg_24x24, - wind_direction_meteorological_151deg_24x24, - wind_direction_meteorological_152deg_24x24, - wind_direction_meteorological_153deg_24x24, - wind_direction_meteorological_154deg_24x24, - wind_direction_meteorological_155deg_24x24, - wind_direction_meteorological_156deg_24x24, - wind_direction_meteorological_157deg_24x24, - wind_direction_meteorological_158deg_24x24, - wind_direction_meteorological_159deg_24x24, - wind_direction_meteorological_160deg_24x24, - wind_direction_meteorological_161deg_24x24, - wind_direction_meteorological_162deg_24x24, - wind_direction_meteorological_163deg_24x24, - wind_direction_meteorological_164deg_24x24, - wind_direction_meteorological_165deg_24x24, - wind_direction_meteorological_166deg_24x24, - wind_direction_meteorological_167deg_24x24, - wind_direction_meteorological_168deg_24x24, - wind_direction_meteorological_169deg_24x24, - wind_direction_meteorological_170deg_24x24, - wind_direction_meteorological_171deg_24x24, - wind_direction_meteorological_172deg_24x24, - wind_direction_meteorological_173deg_24x24, - wind_direction_meteorological_174deg_24x24, - wind_direction_meteorological_175deg_24x24, - wind_direction_meteorological_176deg_24x24, - wind_direction_meteorological_177deg_24x24, - wind_direction_meteorological_178deg_24x24, - wind_direction_meteorological_179deg_24x24, - wind_direction_meteorological_180deg_24x24, - wind_direction_meteorological_181deg_24x24, - wind_direction_meteorological_182deg_24x24, - wind_direction_meteorological_183deg_24x24, - wind_direction_meteorological_184deg_24x24, - wind_direction_meteorological_185deg_24x24, - wind_direction_meteorological_186deg_24x24, - wind_direction_meteorological_187deg_24x24, - wind_direction_meteorological_188deg_24x24, - wind_direction_meteorological_189deg_24x24, - wind_direction_meteorological_190deg_24x24, - wind_direction_meteorological_191deg_24x24, - wind_direction_meteorological_192deg_24x24, - wind_direction_meteorological_193deg_24x24, - wind_direction_meteorological_194deg_24x24, - wind_direction_meteorological_195deg_24x24, - wind_direction_meteorological_196deg_24x24, - wind_direction_meteorological_197deg_24x24, - wind_direction_meteorological_198deg_24x24, - wind_direction_meteorological_199deg_24x24, - wind_direction_meteorological_200deg_24x24, - wind_direction_meteorological_201deg_24x24, - wind_direction_meteorological_202deg_24x24, - wind_direction_meteorological_203deg_24x24, - wind_direction_meteorological_204deg_24x24, - wind_direction_meteorological_205deg_24x24, - wind_direction_meteorological_206deg_24x24, - wind_direction_meteorological_207deg_24x24, - wind_direction_meteorological_208deg_24x24, - wind_direction_meteorological_209deg_24x24, - wind_direction_meteorological_210deg_24x24, - wind_direction_meteorological_211deg_24x24, - wind_direction_meteorological_212deg_24x24, - wind_direction_meteorological_213deg_24x24, - wind_direction_meteorological_214deg_24x24, - wind_direction_meteorological_215deg_24x24, - wind_direction_meteorological_216deg_24x24, - wind_direction_meteorological_217deg_24x24, - wind_direction_meteorological_218deg_24x24, - wind_direction_meteorological_219deg_24x24, - wind_direction_meteorological_220deg_24x24, - wind_direction_meteorological_221deg_24x24, - wind_direction_meteorological_222deg_24x24, - wind_direction_meteorological_223deg_24x24, - wind_direction_meteorological_224deg_24x24, - wind_direction_meteorological_225deg_24x24, - wind_direction_meteorological_226deg_24x24, - wind_direction_meteorological_227deg_24x24, - wind_direction_meteorological_228deg_24x24, - wind_direction_meteorological_229deg_24x24, - wind_direction_meteorological_230deg_24x24, - wind_direction_meteorological_231deg_24x24, - wind_direction_meteorological_232deg_24x24, - wind_direction_meteorological_233deg_24x24, - wind_direction_meteorological_234deg_24x24, - wind_direction_meteorological_235deg_24x24, - wind_direction_meteorological_236deg_24x24, - wind_direction_meteorological_237deg_24x24, - wind_direction_meteorological_238deg_24x24, - wind_direction_meteorological_239deg_24x24, - wind_direction_meteorological_240deg_24x24, - wind_direction_meteorological_241deg_24x24, - wind_direction_meteorological_242deg_24x24, - wind_direction_meteorological_243deg_24x24, - wind_direction_meteorological_244deg_24x24, - wind_direction_meteorological_245deg_24x24, - wind_direction_meteorological_246deg_24x24, - wind_direction_meteorological_247deg_24x24, - wind_direction_meteorological_248deg_24x24, - wind_direction_meteorological_249deg_24x24, - wind_direction_meteorological_250deg_24x24, - wind_direction_meteorological_251deg_24x24, - wind_direction_meteorological_252deg_24x24, - wind_direction_meteorological_253deg_24x24, - wind_direction_meteorological_254deg_24x24, - wind_direction_meteorological_255deg_24x24, - wind_direction_meteorological_256deg_24x24, - wind_direction_meteorological_257deg_24x24, - wind_direction_meteorological_258deg_24x24, - wind_direction_meteorological_259deg_24x24, - wind_direction_meteorological_260deg_24x24, - wind_direction_meteorological_261deg_24x24, - wind_direction_meteorological_262deg_24x24, - wind_direction_meteorological_263deg_24x24, - wind_direction_meteorological_264deg_24x24, - wind_direction_meteorological_265deg_24x24, - wind_direction_meteorological_266deg_24x24, - wind_direction_meteorological_267deg_24x24, - wind_direction_meteorological_268deg_24x24, - wind_direction_meteorological_269deg_24x24, - wind_direction_meteorological_270deg_24x24, - wind_direction_meteorological_271deg_24x24, - wind_direction_meteorological_272deg_24x24, - wind_direction_meteorological_273deg_24x24, - wind_direction_meteorological_274deg_24x24, - wind_direction_meteorological_275deg_24x24, - wind_direction_meteorological_276deg_24x24, - wind_direction_meteorological_277deg_24x24, - wind_direction_meteorological_278deg_24x24, - wind_direction_meteorological_279deg_24x24, - wind_direction_meteorological_280deg_24x24, - wind_direction_meteorological_281deg_24x24, - wind_direction_meteorological_282deg_24x24, - wind_direction_meteorological_283deg_24x24, - wind_direction_meteorological_284deg_24x24, - wind_direction_meteorological_285deg_24x24, - wind_direction_meteorological_286deg_24x24, - wind_direction_meteorological_287deg_24x24, - wind_direction_meteorological_288deg_24x24, - wind_direction_meteorological_289deg_24x24, - wind_direction_meteorological_290deg_24x24, - wind_direction_meteorological_291deg_24x24, - wind_direction_meteorological_292deg_24x24, - wind_direction_meteorological_293deg_24x24, - wind_direction_meteorological_294deg_24x24, - wind_direction_meteorological_295deg_24x24, - wind_direction_meteorological_296deg_24x24, - wind_direction_meteorological_297deg_24x24, - wind_direction_meteorological_298deg_24x24, - wind_direction_meteorological_299deg_24x24, - wind_direction_meteorological_300deg_24x24, - wind_direction_meteorological_301deg_24x24, - wind_direction_meteorological_302deg_24x24, - wind_direction_meteorological_303deg_24x24, - wind_direction_meteorological_304deg_24x24, - wind_direction_meteorological_305deg_24x24, - wind_direction_meteorological_306deg_24x24, - wind_direction_meteorological_307deg_24x24, - wind_direction_meteorological_308deg_24x24, - wind_direction_meteorological_309deg_24x24, - wind_direction_meteorological_310deg_24x24, - wind_direction_meteorological_311deg_24x24, - wind_direction_meteorological_312deg_24x24, - wind_direction_meteorological_313deg_24x24, - wind_direction_meteorological_314deg_24x24, - wind_direction_meteorological_315deg_24x24, - wind_direction_meteorological_316deg_24x24, - wind_direction_meteorological_317deg_24x24, - wind_direction_meteorological_318deg_24x24, - wind_direction_meteorological_319deg_24x24, - wind_direction_meteorological_320deg_24x24, - wind_direction_meteorological_321deg_24x24, - wind_direction_meteorological_322deg_24x24, - wind_direction_meteorological_323deg_24x24, - wind_direction_meteorological_324deg_24x24, - wind_direction_meteorological_325deg_24x24, - wind_direction_meteorological_326deg_24x24, - wind_direction_meteorological_327deg_24x24, - wind_direction_meteorological_328deg_24x24, - wind_direction_meteorological_329deg_24x24, - wind_direction_meteorological_330deg_24x24, - wind_direction_meteorological_331deg_24x24, - wind_direction_meteorological_332deg_24x24, - wind_direction_meteorological_333deg_24x24, - wind_direction_meteorological_334deg_24x24, - wind_direction_meteorological_335deg_24x24, - wind_direction_meteorological_336deg_24x24, - wind_direction_meteorological_337deg_24x24, - wind_direction_meteorological_338deg_24x24, - wind_direction_meteorological_339deg_24x24, - wind_direction_meteorological_340deg_24x24, - wind_direction_meteorological_341deg_24x24, - wind_direction_meteorological_342deg_24x24, - wind_direction_meteorological_343deg_24x24, - wind_direction_meteorological_344deg_24x24, - wind_direction_meteorological_345deg_24x24, - wind_direction_meteorological_346deg_24x24, - wind_direction_meteorological_347deg_24x24, - wind_direction_meteorological_348deg_24x24, - wind_direction_meteorological_349deg_24x24, - wind_direction_meteorological_350deg_24x24, - wind_direction_meteorological_351deg_24x24, - wind_direction_meteorological_352deg_24x24, - wind_direction_meteorological_353deg_24x24, - wind_direction_meteorological_354deg_24x24, - wind_direction_meteorological_355deg_24x24, - wind_direction_meteorological_356deg_24x24, - wind_direction_meteorological_357deg_24x24, - wind_direction_meteorological_358deg_24x24, - wind_direction_meteorological_359deg_24x24}; + wind_direction_meteorological_0deg_24x24, + wind_direction_meteorological_1deg_24x24, + wind_direction_meteorological_2deg_24x24, + wind_direction_meteorological_3deg_24x24, + wind_direction_meteorological_4deg_24x24, + wind_direction_meteorological_5deg_24x24, + wind_direction_meteorological_6deg_24x24, + wind_direction_meteorological_7deg_24x24, + wind_direction_meteorological_8deg_24x24, + wind_direction_meteorological_9deg_24x24, + wind_direction_meteorological_10deg_24x24, + wind_direction_meteorological_11deg_24x24, + wind_direction_meteorological_12deg_24x24, + wind_direction_meteorological_13deg_24x24, + wind_direction_meteorological_14deg_24x24, + wind_direction_meteorological_15deg_24x24, + wind_direction_meteorological_16deg_24x24, + wind_direction_meteorological_17deg_24x24, + wind_direction_meteorological_18deg_24x24, + wind_direction_meteorological_19deg_24x24, + wind_direction_meteorological_20deg_24x24, + wind_direction_meteorological_21deg_24x24, + wind_direction_meteorological_22deg_24x24, + wind_direction_meteorological_23deg_24x24, + wind_direction_meteorological_24deg_24x24, + wind_direction_meteorological_25deg_24x24, + wind_direction_meteorological_26deg_24x24, + wind_direction_meteorological_27deg_24x24, + wind_direction_meteorological_28deg_24x24, + wind_direction_meteorological_29deg_24x24, + wind_direction_meteorological_30deg_24x24, + wind_direction_meteorological_31deg_24x24, + wind_direction_meteorological_32deg_24x24, + wind_direction_meteorological_33deg_24x24, + wind_direction_meteorological_34deg_24x24, + wind_direction_meteorological_35deg_24x24, + wind_direction_meteorological_36deg_24x24, + wind_direction_meteorological_37deg_24x24, + wind_direction_meteorological_38deg_24x24, + wind_direction_meteorological_39deg_24x24, + wind_direction_meteorological_40deg_24x24, + wind_direction_meteorological_41deg_24x24, + wind_direction_meteorological_42deg_24x24, + wind_direction_meteorological_43deg_24x24, + wind_direction_meteorological_44deg_24x24, + wind_direction_meteorological_45deg_24x24, + wind_direction_meteorological_46deg_24x24, + wind_direction_meteorological_47deg_24x24, + wind_direction_meteorological_48deg_24x24, + wind_direction_meteorological_49deg_24x24, + wind_direction_meteorological_50deg_24x24, + wind_direction_meteorological_51deg_24x24, + wind_direction_meteorological_52deg_24x24, + wind_direction_meteorological_53deg_24x24, + wind_direction_meteorological_54deg_24x24, + wind_direction_meteorological_55deg_24x24, + wind_direction_meteorological_56deg_24x24, + wind_direction_meteorological_57deg_24x24, + wind_direction_meteorological_58deg_24x24, + wind_direction_meteorological_59deg_24x24, + wind_direction_meteorological_60deg_24x24, + wind_direction_meteorological_61deg_24x24, + wind_direction_meteorological_62deg_24x24, + wind_direction_meteorological_63deg_24x24, + wind_direction_meteorological_64deg_24x24, + wind_direction_meteorological_65deg_24x24, + wind_direction_meteorological_66deg_24x24, + wind_direction_meteorological_67deg_24x24, + wind_direction_meteorological_68deg_24x24, + wind_direction_meteorological_69deg_24x24, + wind_direction_meteorological_70deg_24x24, + wind_direction_meteorological_71deg_24x24, + wind_direction_meteorological_72deg_24x24, + wind_direction_meteorological_73deg_24x24, + wind_direction_meteorological_74deg_24x24, + wind_direction_meteorological_75deg_24x24, + wind_direction_meteorological_76deg_24x24, + wind_direction_meteorological_77deg_24x24, + wind_direction_meteorological_78deg_24x24, + wind_direction_meteorological_79deg_24x24, + wind_direction_meteorological_80deg_24x24, + wind_direction_meteorological_81deg_24x24, + wind_direction_meteorological_82deg_24x24, + wind_direction_meteorological_83deg_24x24, + wind_direction_meteorological_84deg_24x24, + wind_direction_meteorological_85deg_24x24, + wind_direction_meteorological_86deg_24x24, + wind_direction_meteorological_87deg_24x24, + wind_direction_meteorological_88deg_24x24, + wind_direction_meteorological_89deg_24x24, + wind_direction_meteorological_90deg_24x24, + wind_direction_meteorological_91deg_24x24, + wind_direction_meteorological_92deg_24x24, + wind_direction_meteorological_93deg_24x24, + wind_direction_meteorological_94deg_24x24, + wind_direction_meteorological_95deg_24x24, + wind_direction_meteorological_96deg_24x24, + wind_direction_meteorological_97deg_24x24, + wind_direction_meteorological_98deg_24x24, + wind_direction_meteorological_99deg_24x24, + wind_direction_meteorological_100deg_24x24, + wind_direction_meteorological_101deg_24x24, + wind_direction_meteorological_102deg_24x24, + wind_direction_meteorological_103deg_24x24, + wind_direction_meteorological_104deg_24x24, + wind_direction_meteorological_105deg_24x24, + wind_direction_meteorological_106deg_24x24, + wind_direction_meteorological_107deg_24x24, + wind_direction_meteorological_108deg_24x24, + wind_direction_meteorological_109deg_24x24, + wind_direction_meteorological_110deg_24x24, + wind_direction_meteorological_111deg_24x24, + wind_direction_meteorological_112deg_24x24, + wind_direction_meteorological_113deg_24x24, + wind_direction_meteorological_114deg_24x24, + wind_direction_meteorological_115deg_24x24, + wind_direction_meteorological_116deg_24x24, + wind_direction_meteorological_117deg_24x24, + wind_direction_meteorological_118deg_24x24, + wind_direction_meteorological_119deg_24x24, + wind_direction_meteorological_120deg_24x24, + wind_direction_meteorological_121deg_24x24, + wind_direction_meteorological_122deg_24x24, + wind_direction_meteorological_123deg_24x24, + wind_direction_meteorological_124deg_24x24, + wind_direction_meteorological_125deg_24x24, + wind_direction_meteorological_126deg_24x24, + wind_direction_meteorological_127deg_24x24, + wind_direction_meteorological_128deg_24x24, + wind_direction_meteorological_129deg_24x24, + wind_direction_meteorological_130deg_24x24, + wind_direction_meteorological_131deg_24x24, + wind_direction_meteorological_132deg_24x24, + wind_direction_meteorological_133deg_24x24, + wind_direction_meteorological_134deg_24x24, + wind_direction_meteorological_135deg_24x24, + wind_direction_meteorological_136deg_24x24, + wind_direction_meteorological_137deg_24x24, + wind_direction_meteorological_138deg_24x24, + wind_direction_meteorological_139deg_24x24, + wind_direction_meteorological_140deg_24x24, + wind_direction_meteorological_141deg_24x24, + wind_direction_meteorological_142deg_24x24, + wind_direction_meteorological_143deg_24x24, + wind_direction_meteorological_144deg_24x24, + wind_direction_meteorological_145deg_24x24, + wind_direction_meteorological_146deg_24x24, + wind_direction_meteorological_147deg_24x24, + wind_direction_meteorological_148deg_24x24, + wind_direction_meteorological_149deg_24x24, + wind_direction_meteorological_150deg_24x24, + wind_direction_meteorological_151deg_24x24, + wind_direction_meteorological_152deg_24x24, + wind_direction_meteorological_153deg_24x24, + wind_direction_meteorological_154deg_24x24, + wind_direction_meteorological_155deg_24x24, + wind_direction_meteorological_156deg_24x24, + wind_direction_meteorological_157deg_24x24, + wind_direction_meteorological_158deg_24x24, + wind_direction_meteorological_159deg_24x24, + wind_direction_meteorological_160deg_24x24, + wind_direction_meteorological_161deg_24x24, + wind_direction_meteorological_162deg_24x24, + wind_direction_meteorological_163deg_24x24, + wind_direction_meteorological_164deg_24x24, + wind_direction_meteorological_165deg_24x24, + wind_direction_meteorological_166deg_24x24, + wind_direction_meteorological_167deg_24x24, + wind_direction_meteorological_168deg_24x24, + wind_direction_meteorological_169deg_24x24, + wind_direction_meteorological_170deg_24x24, + wind_direction_meteorological_171deg_24x24, + wind_direction_meteorological_172deg_24x24, + wind_direction_meteorological_173deg_24x24, + wind_direction_meteorological_174deg_24x24, + wind_direction_meteorological_175deg_24x24, + wind_direction_meteorological_176deg_24x24, + wind_direction_meteorological_177deg_24x24, + wind_direction_meteorological_178deg_24x24, + wind_direction_meteorological_179deg_24x24, + wind_direction_meteorological_180deg_24x24, + wind_direction_meteorological_181deg_24x24, + wind_direction_meteorological_182deg_24x24, + wind_direction_meteorological_183deg_24x24, + wind_direction_meteorological_184deg_24x24, + wind_direction_meteorological_185deg_24x24, + wind_direction_meteorological_186deg_24x24, + wind_direction_meteorological_187deg_24x24, + wind_direction_meteorological_188deg_24x24, + wind_direction_meteorological_189deg_24x24, + wind_direction_meteorological_190deg_24x24, + wind_direction_meteorological_191deg_24x24, + wind_direction_meteorological_192deg_24x24, + wind_direction_meteorological_193deg_24x24, + wind_direction_meteorological_194deg_24x24, + wind_direction_meteorological_195deg_24x24, + wind_direction_meteorological_196deg_24x24, + wind_direction_meteorological_197deg_24x24, + wind_direction_meteorological_198deg_24x24, + wind_direction_meteorological_199deg_24x24, + wind_direction_meteorological_200deg_24x24, + wind_direction_meteorological_201deg_24x24, + wind_direction_meteorological_202deg_24x24, + wind_direction_meteorological_203deg_24x24, + wind_direction_meteorological_204deg_24x24, + wind_direction_meteorological_205deg_24x24, + wind_direction_meteorological_206deg_24x24, + wind_direction_meteorological_207deg_24x24, + wind_direction_meteorological_208deg_24x24, + wind_direction_meteorological_209deg_24x24, + wind_direction_meteorological_210deg_24x24, + wind_direction_meteorological_211deg_24x24, + wind_direction_meteorological_212deg_24x24, + wind_direction_meteorological_213deg_24x24, + wind_direction_meteorological_214deg_24x24, + wind_direction_meteorological_215deg_24x24, + wind_direction_meteorological_216deg_24x24, + wind_direction_meteorological_217deg_24x24, + wind_direction_meteorological_218deg_24x24, + wind_direction_meteorological_219deg_24x24, + wind_direction_meteorological_220deg_24x24, + wind_direction_meteorological_221deg_24x24, + wind_direction_meteorological_222deg_24x24, + wind_direction_meteorological_223deg_24x24, + wind_direction_meteorological_224deg_24x24, + wind_direction_meteorological_225deg_24x24, + wind_direction_meteorological_226deg_24x24, + wind_direction_meteorological_227deg_24x24, + wind_direction_meteorological_228deg_24x24, + wind_direction_meteorological_229deg_24x24, + wind_direction_meteorological_230deg_24x24, + wind_direction_meteorological_231deg_24x24, + wind_direction_meteorological_232deg_24x24, + wind_direction_meteorological_233deg_24x24, + wind_direction_meteorological_234deg_24x24, + wind_direction_meteorological_235deg_24x24, + wind_direction_meteorological_236deg_24x24, + wind_direction_meteorological_237deg_24x24, + wind_direction_meteorological_238deg_24x24, + wind_direction_meteorological_239deg_24x24, + wind_direction_meteorological_240deg_24x24, + wind_direction_meteorological_241deg_24x24, + wind_direction_meteorological_242deg_24x24, + wind_direction_meteorological_243deg_24x24, + wind_direction_meteorological_244deg_24x24, + wind_direction_meteorological_245deg_24x24, + wind_direction_meteorological_246deg_24x24, + wind_direction_meteorological_247deg_24x24, + wind_direction_meteorological_248deg_24x24, + wind_direction_meteorological_249deg_24x24, + wind_direction_meteorological_250deg_24x24, + wind_direction_meteorological_251deg_24x24, + wind_direction_meteorological_252deg_24x24, + wind_direction_meteorological_253deg_24x24, + wind_direction_meteorological_254deg_24x24, + wind_direction_meteorological_255deg_24x24, + wind_direction_meteorological_256deg_24x24, + wind_direction_meteorological_257deg_24x24, + wind_direction_meteorological_258deg_24x24, + wind_direction_meteorological_259deg_24x24, + wind_direction_meteorological_260deg_24x24, + wind_direction_meteorological_261deg_24x24, + wind_direction_meteorological_262deg_24x24, + wind_direction_meteorological_263deg_24x24, + wind_direction_meteorological_264deg_24x24, + wind_direction_meteorological_265deg_24x24, + wind_direction_meteorological_266deg_24x24, + wind_direction_meteorological_267deg_24x24, + wind_direction_meteorological_268deg_24x24, + wind_direction_meteorological_269deg_24x24, + wind_direction_meteorological_270deg_24x24, + wind_direction_meteorological_271deg_24x24, + wind_direction_meteorological_272deg_24x24, + wind_direction_meteorological_273deg_24x24, + wind_direction_meteorological_274deg_24x24, + wind_direction_meteorological_275deg_24x24, + wind_direction_meteorological_276deg_24x24, + wind_direction_meteorological_277deg_24x24, + wind_direction_meteorological_278deg_24x24, + wind_direction_meteorological_279deg_24x24, + wind_direction_meteorological_280deg_24x24, + wind_direction_meteorological_281deg_24x24, + wind_direction_meteorological_282deg_24x24, + wind_direction_meteorological_283deg_24x24, + wind_direction_meteorological_284deg_24x24, + wind_direction_meteorological_285deg_24x24, + wind_direction_meteorological_286deg_24x24, + wind_direction_meteorological_287deg_24x24, + wind_direction_meteorological_288deg_24x24, + wind_direction_meteorological_289deg_24x24, + wind_direction_meteorological_290deg_24x24, + wind_direction_meteorological_291deg_24x24, + wind_direction_meteorological_292deg_24x24, + wind_direction_meteorological_293deg_24x24, + wind_direction_meteorological_294deg_24x24, + wind_direction_meteorological_295deg_24x24, + wind_direction_meteorological_296deg_24x24, + wind_direction_meteorological_297deg_24x24, + wind_direction_meteorological_298deg_24x24, + wind_direction_meteorological_299deg_24x24, + wind_direction_meteorological_300deg_24x24, + wind_direction_meteorological_301deg_24x24, + wind_direction_meteorological_302deg_24x24, + wind_direction_meteorological_303deg_24x24, + wind_direction_meteorological_304deg_24x24, + wind_direction_meteorological_305deg_24x24, + wind_direction_meteorological_306deg_24x24, + wind_direction_meteorological_307deg_24x24, + wind_direction_meteorological_308deg_24x24, + wind_direction_meteorological_309deg_24x24, + wind_direction_meteorological_310deg_24x24, + wind_direction_meteorological_311deg_24x24, + wind_direction_meteorological_312deg_24x24, + wind_direction_meteorological_313deg_24x24, + wind_direction_meteorological_314deg_24x24, + wind_direction_meteorological_315deg_24x24, + wind_direction_meteorological_316deg_24x24, + wind_direction_meteorological_317deg_24x24, + wind_direction_meteorological_318deg_24x24, + wind_direction_meteorological_319deg_24x24, + wind_direction_meteorological_320deg_24x24, + wind_direction_meteorological_321deg_24x24, + wind_direction_meteorological_322deg_24x24, + wind_direction_meteorological_323deg_24x24, + wind_direction_meteorological_324deg_24x24, + wind_direction_meteorological_325deg_24x24, + wind_direction_meteorological_326deg_24x24, + wind_direction_meteorological_327deg_24x24, + wind_direction_meteorological_328deg_24x24, + wind_direction_meteorological_329deg_24x24, + wind_direction_meteorological_330deg_24x24, + wind_direction_meteorological_331deg_24x24, + wind_direction_meteorological_332deg_24x24, + wind_direction_meteorological_333deg_24x24, + wind_direction_meteorological_334deg_24x24, + wind_direction_meteorological_335deg_24x24, + wind_direction_meteorological_336deg_24x24, + wind_direction_meteorological_337deg_24x24, + wind_direction_meteorological_338deg_24x24, + wind_direction_meteorological_339deg_24x24, + wind_direction_meteorological_340deg_24x24, + wind_direction_meteorological_341deg_24x24, + wind_direction_meteorological_342deg_24x24, + wind_direction_meteorological_343deg_24x24, + wind_direction_meteorological_344deg_24x24, + wind_direction_meteorological_345deg_24x24, + wind_direction_meteorological_346deg_24x24, + wind_direction_meteorological_347deg_24x24, + wind_direction_meteorological_348deg_24x24, + wind_direction_meteorological_349deg_24x24, + wind_direction_meteorological_350deg_24x24, + wind_direction_meteorological_351deg_24x24, + wind_direction_meteorological_352deg_24x24, + wind_direction_meteorological_353deg_24x24, + wind_direction_meteorological_354deg_24x24, + wind_direction_meteorological_355deg_24x24, + wind_direction_meteorological_356deg_24x24, + wind_direction_meteorological_357deg_24x24, + wind_direction_meteorological_358deg_24x24, + wind_direction_meteorological_359deg_24x24}; #endif // end WIND_ICONS_360 /* Returns a 24x24 wind direction icon bitmap for angles 0 to 359 degrees @@ -1327,10 +1745,8 @@ const uint8_t *getWindBitmap24(int windDeg) { windDeg %= 360; // enforce domain // number of directions - int n = sizeof(wind_direction_icon_arr) - / sizeof(wind_direction_icon_arr[0]); - int arr_offset = (int) ( (windDeg + (360 / n / 2)) % 360 ) - / ( 360 / (float) n ); + int n = sizeof(wind_direction_icon_arr) / sizeof(wind_direction_icon_arr[0]); + int arr_offset = (int)((windDeg + (360 / n / 2)) % 360) / (360 / (float)n); return wind_direction_icon_arr[arr_offset]; } // end getWindBitmap24 @@ -1359,8 +1775,7 @@ const char *getCompassPointNotation(int windDeg) #endif windDeg %= 360; // enforce domain - int arr_offset = (int) (windDeg / ( 360 / (float) precision )) - * ( 32 / precision) ; + int arr_offset = (int)(windDeg / (360 / (float)precision)) * (32 / precision); return COMPASS_POINT_NOTATION[arr_offset]; } // end getCompassPointNotation @@ -1378,7 +1793,7 @@ const char *getCompassPointNotation(int windDeg) * * ArduinoJson DeserializationError codes [-256, -511] * https://arduinojson.org/v6/api/misc/deserializationerror/ - * + * * WiFi Status codes [-512, -767] * https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiType.h */ @@ -1387,114 +1802,201 @@ const char *getHttpResponsePhrase(int code) switch (code) { // 1xx - Informational Responses - case 100: return TXT_HTTP_RESPONSE_100; - case 101: return TXT_HTTP_RESPONSE_101; - case 102: return TXT_HTTP_RESPONSE_102; - case 103: return TXT_HTTP_RESPONSE_103; + case 100: + return TXT_HTTP_RESPONSE_100; + case 101: + return TXT_HTTP_RESPONSE_101; + case 102: + return TXT_HTTP_RESPONSE_102; + case 103: + return TXT_HTTP_RESPONSE_103; // 2xx - Successful Responses - case 200: return TXT_HTTP_RESPONSE_200; - case 201: return TXT_HTTP_RESPONSE_201; - case 202: return TXT_HTTP_RESPONSE_202; - case 203: return TXT_HTTP_RESPONSE_203; - case 204: return TXT_HTTP_RESPONSE_204; - case 205: return TXT_HTTP_RESPONSE_205; - case 206: return TXT_HTTP_RESPONSE_206; - case 207: return TXT_HTTP_RESPONSE_207; - case 208: return TXT_HTTP_RESPONSE_208; - case 226: return TXT_HTTP_RESPONSE_226; + case 200: + return TXT_HTTP_RESPONSE_200; + case 201: + return TXT_HTTP_RESPONSE_201; + case 202: + return TXT_HTTP_RESPONSE_202; + case 203: + return TXT_HTTP_RESPONSE_203; + case 204: + return TXT_HTTP_RESPONSE_204; + case 205: + return TXT_HTTP_RESPONSE_205; + case 206: + return TXT_HTTP_RESPONSE_206; + case 207: + return TXT_HTTP_RESPONSE_207; + case 208: + return TXT_HTTP_RESPONSE_208; + case 226: + return TXT_HTTP_RESPONSE_226; // 3xx - Redirection Responses - case 300: return TXT_HTTP_RESPONSE_300; - case 301: return TXT_HTTP_RESPONSE_301; - case 302: return TXT_HTTP_RESPONSE_302; - case 303: return TXT_HTTP_RESPONSE_303; - case 304: return TXT_HTTP_RESPONSE_304; - case 305: return TXT_HTTP_RESPONSE_305; - case 307: return TXT_HTTP_RESPONSE_307; - case 308: return TXT_HTTP_RESPONSE_308; + case 300: + return TXT_HTTP_RESPONSE_300; + case 301: + return TXT_HTTP_RESPONSE_301; + case 302: + return TXT_HTTP_RESPONSE_302; + case 303: + return TXT_HTTP_RESPONSE_303; + case 304: + return TXT_HTTP_RESPONSE_304; + case 305: + return TXT_HTTP_RESPONSE_305; + case 307: + return TXT_HTTP_RESPONSE_307; + case 308: + return TXT_HTTP_RESPONSE_308; // 4xx - Client Error Responses - case 400: return TXT_HTTP_RESPONSE_400; - case 401: return TXT_HTTP_RESPONSE_401; - case 402: return TXT_HTTP_RESPONSE_402; - case 403: return TXT_HTTP_RESPONSE_403; - case 404: return TXT_HTTP_RESPONSE_404; - case 405: return TXT_HTTP_RESPONSE_405; - case 406: return TXT_HTTP_RESPONSE_406; - case 407: return TXT_HTTP_RESPONSE_407; - case 408: return TXT_HTTP_RESPONSE_408; - case 409: return TXT_HTTP_RESPONSE_409; - case 410: return TXT_HTTP_RESPONSE_410; - case 411: return TXT_HTTP_RESPONSE_411; - case 412: return TXT_HTTP_RESPONSE_412; - case 413: return TXT_HTTP_RESPONSE_413; - case 414: return TXT_HTTP_RESPONSE_414; - case 415: return TXT_HTTP_RESPONSE_415; - case 416: return TXT_HTTP_RESPONSE_416; - case 417: return TXT_HTTP_RESPONSE_417; - case 418: return TXT_HTTP_RESPONSE_418; - case 421: return TXT_HTTP_RESPONSE_421; - case 422: return TXT_HTTP_RESPONSE_422; - case 423: return TXT_HTTP_RESPONSE_423; - case 424: return TXT_HTTP_RESPONSE_424; - case 425: return TXT_HTTP_RESPONSE_425; - case 426: return TXT_HTTP_RESPONSE_426; - case 428: return TXT_HTTP_RESPONSE_428; - case 429: return TXT_HTTP_RESPONSE_429; - case 431: return TXT_HTTP_RESPONSE_431; - case 451: return TXT_HTTP_RESPONSE_451; + case 400: + return TXT_HTTP_RESPONSE_400; + case 401: + return TXT_HTTP_RESPONSE_401; + case 402: + return TXT_HTTP_RESPONSE_402; + case 403: + return TXT_HTTP_RESPONSE_403; + case 404: + return TXT_HTTP_RESPONSE_404; + case 405: + return TXT_HTTP_RESPONSE_405; + case 406: + return TXT_HTTP_RESPONSE_406; + case 407: + return TXT_HTTP_RESPONSE_407; + case 408: + return TXT_HTTP_RESPONSE_408; + case 409: + return TXT_HTTP_RESPONSE_409; + case 410: + return TXT_HTTP_RESPONSE_410; + case 411: + return TXT_HTTP_RESPONSE_411; + case 412: + return TXT_HTTP_RESPONSE_412; + case 413: + return TXT_HTTP_RESPONSE_413; + case 414: + return TXT_HTTP_RESPONSE_414; + case 415: + return TXT_HTTP_RESPONSE_415; + case 416: + return TXT_HTTP_RESPONSE_416; + case 417: + return TXT_HTTP_RESPONSE_417; + case 418: + return TXT_HTTP_RESPONSE_418; + case 421: + return TXT_HTTP_RESPONSE_421; + case 422: + return TXT_HTTP_RESPONSE_422; + case 423: + return TXT_HTTP_RESPONSE_423; + case 424: + return TXT_HTTP_RESPONSE_424; + case 425: + return TXT_HTTP_RESPONSE_425; + case 426: + return TXT_HTTP_RESPONSE_426; + case 428: + return TXT_HTTP_RESPONSE_428; + case 429: + return TXT_HTTP_RESPONSE_429; + case 431: + return TXT_HTTP_RESPONSE_431; + case 451: + return TXT_HTTP_RESPONSE_451; // 5xx - Server Error Responses - case 500: return TXT_HTTP_RESPONSE_500; - case 501: return TXT_HTTP_RESPONSE_501; - case 502: return TXT_HTTP_RESPONSE_502; - case 503: return TXT_HTTP_RESPONSE_503; - case 504: return TXT_HTTP_RESPONSE_504; - case 505: return TXT_HTTP_RESPONSE_505; - case 506: return TXT_HTTP_RESPONSE_506; - case 507: return TXT_HTTP_RESPONSE_507; - case 508: return TXT_HTTP_RESPONSE_508; - case 510: return TXT_HTTP_RESPONSE_510; - case 511: return TXT_HTTP_RESPONSE_511; + case 500: + return TXT_HTTP_RESPONSE_500; + case 501: + return TXT_HTTP_RESPONSE_501; + case 502: + return TXT_HTTP_RESPONSE_502; + case 503: + return TXT_HTTP_RESPONSE_503; + case 504: + return TXT_HTTP_RESPONSE_504; + case 505: + return TXT_HTTP_RESPONSE_505; + case 506: + return TXT_HTTP_RESPONSE_506; + case 507: + return TXT_HTTP_RESPONSE_507; + case 508: + return TXT_HTTP_RESPONSE_508; + case 510: + return TXT_HTTP_RESPONSE_510; + case 511: + return TXT_HTTP_RESPONSE_511; // HTTP client errors [0, -255] - case HTTPC_ERROR_CONNECTION_REFUSED: return TXT_HTTPC_ERROR_CONNECTION_REFUSED; - case HTTPC_ERROR_SEND_HEADER_FAILED: return TXT_HTTPC_ERROR_SEND_HEADER_FAILED; - case HTTPC_ERROR_SEND_PAYLOAD_FAILED: return TXT_HTTPC_ERROR_SEND_PAYLOAD_FAILED; - case HTTPC_ERROR_NOT_CONNECTED: return TXT_HTTPC_ERROR_NOT_CONNECTED; - case HTTPC_ERROR_CONNECTION_LOST: return TXT_HTTPC_ERROR_CONNECTION_LOST; - case HTTPC_ERROR_NO_STREAM: return TXT_HTTPC_ERROR_NO_STREAM; - case HTTPC_ERROR_NO_HTTP_SERVER: return TXT_HTTPC_ERROR_NO_HTTP_SERVER; - case HTTPC_ERROR_TOO_LESS_RAM: return TXT_HTTPC_ERROR_TOO_LESS_RAM; - case HTTPC_ERROR_ENCODING: return TXT_HTTPC_ERROR_ENCODING; - case HTTPC_ERROR_STREAM_WRITE: return TXT_HTTPC_ERROR_STREAM_WRITE; - case HTTPC_ERROR_READ_TIMEOUT: return TXT_HTTPC_ERROR_READ_TIMEOUT; + case HTTPC_ERROR_CONNECTION_REFUSED: + return TXT_HTTPC_ERROR_CONNECTION_REFUSED; + case HTTPC_ERROR_SEND_HEADER_FAILED: + return TXT_HTTPC_ERROR_SEND_HEADER_FAILED; + case HTTPC_ERROR_SEND_PAYLOAD_FAILED: + return TXT_HTTPC_ERROR_SEND_PAYLOAD_FAILED; + case HTTPC_ERROR_NOT_CONNECTED: + return TXT_HTTPC_ERROR_NOT_CONNECTED; + case HTTPC_ERROR_CONNECTION_LOST: + return TXT_HTTPC_ERROR_CONNECTION_LOST; + case HTTPC_ERROR_NO_STREAM: + return TXT_HTTPC_ERROR_NO_STREAM; + case HTTPC_ERROR_NO_HTTP_SERVER: + return TXT_HTTPC_ERROR_NO_HTTP_SERVER; + case HTTPC_ERROR_TOO_LESS_RAM: + return TXT_HTTPC_ERROR_TOO_LESS_RAM; + case HTTPC_ERROR_ENCODING: + return TXT_HTTPC_ERROR_ENCODING; + case HTTPC_ERROR_STREAM_WRITE: + return TXT_HTTPC_ERROR_STREAM_WRITE; + case HTTPC_ERROR_READ_TIMEOUT: + return TXT_HTTPC_ERROR_READ_TIMEOUT; // ArduinoJson DeserializationError codes [-256, -511] - case -256 - (DeserializationError::Code::Ok): return TXT_DESERIALIZATION_ERROR_OK; - case -256 - (DeserializationError::Code::EmptyInput): return TXT_DESERIALIZATION_ERROR_EMPTY_INPUT; - case -256 - (DeserializationError::Code::IncompleteInput): return TXT_DESERIALIZATION_ERROR_INCOMPLETE_INPUT; - case -256 - (DeserializationError::Code::InvalidInput): return TXT_DESERIALIZATION_ERROR_INVALID_INPUT; - case -256 - (DeserializationError::Code::NoMemory): return TXT_DESERIALIZATION_ERROR_NO_MEMORY; - case -256 - (DeserializationError::Code::TooDeep): return TXT_DESERIALIZATION_ERROR_TOO_DEEP; + case -256 - (DeserializationError::Code::Ok): + return TXT_DESERIALIZATION_ERROR_OK; + case -256 - (DeserializationError::Code::EmptyInput): + return TXT_DESERIALIZATION_ERROR_EMPTY_INPUT; + case -256 - (DeserializationError::Code::IncompleteInput): + return TXT_DESERIALIZATION_ERROR_INCOMPLETE_INPUT; + case -256 - (DeserializationError::Code::InvalidInput): + return TXT_DESERIALIZATION_ERROR_INVALID_INPUT; + case -256 - (DeserializationError::Code::NoMemory): + return TXT_DESERIALIZATION_ERROR_NO_MEMORY; + case -256 - (DeserializationError::Code::TooDeep): + return TXT_DESERIALIZATION_ERROR_TOO_DEEP; // WiFi Status codes [-512, -767] - case -512 - WL_NO_SHIELD: return TXT_WL_NO_SHIELD; + case -512 - WL_NO_SHIELD: + return TXT_WL_NO_SHIELD; // case -512 - WL_STOPPED: return TXT_WL_STOPPED; // future - case -512 - WL_IDLE_STATUS: return TXT_WL_IDLE_STATUS; - case -512 - WL_NO_SSID_AVAIL: return TXT_WL_NO_SSID_AVAIL; - case -512 - WL_SCAN_COMPLETED: return TXT_WL_SCAN_COMPLETED; - case -512 - WL_CONNECTED: return TXT_WL_CONNECTED; - case -512 - WL_CONNECT_FAILED: return TXT_WL_CONNECT_FAILED; - case -512 - WL_CONNECTION_LOST: return TXT_WL_CONNECTION_LOST; - case -512 - WL_DISCONNECTED: return TXT_WL_DISCONNECTED; + case -512 - WL_IDLE_STATUS: + return TXT_WL_IDLE_STATUS; + case -512 - WL_NO_SSID_AVAIL: + return TXT_WL_NO_SSID_AVAIL; + case -512 - WL_SCAN_COMPLETED: + return TXT_WL_SCAN_COMPLETED; + case -512 - WL_CONNECTED: + return TXT_WL_CONNECTED; + case -512 - WL_CONNECT_FAILED: + return TXT_WL_CONNECT_FAILED; + case -512 - WL_CONNECTION_LOST: + return TXT_WL_CONNECTION_LOST; + case -512 - WL_DISCONNECTED: + return TXT_WL_DISCONNECTED; - default: return ""; + default: + return ""; } } // end getHttpResponsePhrase - /* This function returns a pointer to a string representing the meaning for a * WiFi status (wl_status_t). * @@ -1505,17 +2007,26 @@ const char *getWifiStatusPhrase(wl_status_t status) { switch (status) { - case WL_NO_SHIELD: return TXT_WL_NO_SHIELD; + case WL_NO_SHIELD: + return TXT_WL_NO_SHIELD; // case WL_STOPPED: return TXT_WL_STOPPED; // future - case WL_IDLE_STATUS: return TXT_WL_IDLE_STATUS; - case WL_NO_SSID_AVAIL: return TXT_WL_NO_SSID_AVAIL; - case WL_SCAN_COMPLETED: return TXT_WL_SCAN_COMPLETED; - case WL_CONNECTED: return TXT_WL_CONNECTED; - case WL_CONNECT_FAILED: return TXT_WL_CONNECT_FAILED; - case WL_CONNECTION_LOST: return TXT_WL_CONNECTION_LOST; - case WL_DISCONNECTED: return TXT_WL_DISCONNECTED; - - default: return ""; + case WL_IDLE_STATUS: + return TXT_WL_IDLE_STATUS; + case WL_NO_SSID_AVAIL: + return TXT_WL_NO_SSID_AVAIL; + case WL_SCAN_COMPLETED: + return TXT_WL_SCAN_COMPLETED; + case WL_CONNECTED: + return TXT_WL_CONNECTED; + case WL_CONNECT_FAILED: + return TXT_WL_CONNECT_FAILED; + case WL_CONNECTION_LOST: + return TXT_WL_CONNECTION_LOST; + case WL_DISCONNECTED: + return TXT_WL_DISCONNECTED; + + default: + return ""; } } // end getWifiStatusPhrase diff --git a/platformio/src/main.cpp b/platformio/src/main.cpp index 6b6865791..020b33ac1 100644 --- a/platformio/src/main.cpp +++ b/platformio/src/main.cpp @@ -31,14 +31,14 @@ #include "icons/icons_196x196.h" #include "renderer.h" #if defined(USE_HTTPS_WITH_CERT_VERIF) || defined(USE_HTTPS_WITH_CERT_VERIF) - #include +#include #endif #ifdef USE_HTTPS_WITH_CERT_VERIF - #include "cert.h" +#include "cert.h" #endif // too large to allocate locally on stack -static owm_resp_onecall_t owm_onecall; +static owm_resp_onecall_t owm_onecall; static owm_resp_air_pollution_t owm_air_pollution; Preferences prefs; @@ -68,17 +68,14 @@ void beginDeepSleep(unsigned long startTime, tm *timeInfo) // time is relative to wake time int curHour = (timeInfo->tm_hour - WAKE_TIME + 24) % 24; const int curMinute = curHour * 60 + timeInfo->tm_min; - const int curSecond = curHour * 3600 - + timeInfo->tm_min * 60 - + timeInfo->tm_sec; + const int curSecond = curHour * 3600 + timeInfo->tm_min * 60 + timeInfo->tm_sec; const int desiredSleepSeconds = SLEEP_DURATION * 60; const int offsetMinutes = curMinute % SLEEP_DURATION; const int offsetSeconds = curSecond % desiredSleepSeconds; // align wake time to nearest multiple of SLEEP_DURATION int sleepMinutes = SLEEP_DURATION - offsetMinutes; - if (desiredSleepSeconds - offsetSeconds < 120 - || offsetSeconds / (float)desiredSleepSeconds > 0.95f) + if (desiredSleepSeconds - offsetSeconds < 120 || offsetSeconds / (float)desiredSleepSeconds > 0.95f) { // if we have a sleep time less than 2 minutes OR less 5% SLEEP_DURATION, // skip to next alignment sleepMinutes += SLEEP_DURATION; @@ -96,8 +93,7 @@ void beginDeepSleep(unsigned long startTime, tm *timeInfo) else { const int hoursUntilWake = 24 - curHour; - sleepDuration = hoursUntilWake * 3600ULL - - (timeInfo->tm_min * 60ULL + timeInfo->tm_sec); + sleepDuration = hoursUntilWake * 3600ULL - (timeInfo->tm_min * 60ULL + timeInfo->tm_sec); } // add extra delay to compensate for esp32's with fast RTCs. @@ -110,7 +106,7 @@ void beginDeepSleep(unsigned long startTime, tm *timeInfo) esp_sleep_enable_timer_wakeup(sleepDuration * 1000000ULL); Serial.print(TXT_AWAKE_FOR); - Serial.println(" " + String((millis() - startTime) / 1000.0, 3) + "s"); + Serial.println(" " + String((millis() - startTime) / 1000.0, 3) + "s"); Serial.print(TXT_ENTERING_DEEP_SLEEP_FOR); Serial.println(" " + String(sleepDuration) + "s"); esp_deep_sleep_start(); @@ -167,16 +163,14 @@ void setup() } else if (batteryVoltage <= VERY_LOW_BATTERY_VOLTAGE) { // very low battery - esp_sleep_enable_timer_wakeup(VERY_LOW_BATTERY_SLEEP_INTERVAL - * 60ULL * 1000000ULL); + esp_sleep_enable_timer_wakeup(VERY_LOW_BATTERY_SLEEP_INTERVAL * 60ULL * 1000000ULL); Serial.println(TXT_VERY_LOW_BATTERY_VOLTAGE); Serial.print(TXT_ENTERING_DEEP_SLEEP_FOR); Serial.println(" " + String(VERY_LOW_BATTERY_SLEEP_INTERVAL) + "min"); } else { // low battery - esp_sleep_enable_timer_wakeup(LOW_BATTERY_SLEEP_INTERVAL - * 60ULL * 1000000ULL); + esp_sleep_enable_timer_wakeup(LOW_BATTERY_SLEEP_INTERVAL * 60ULL * 1000000ULL); Serial.println(TXT_LOW_BATTERY_VOLTAGE); Serial.print(TXT_ENTERING_DEEP_SLEEP_FOR); Serial.println(" " + String(LOW_BATTERY_SLEEP_INTERVAL) + "min"); @@ -252,6 +246,7 @@ void setup() WiFiClientSecure client; client.setCACert(cert_Sectigo_RSA_Domain_Validation_Secure_Server_CA); #endif +#if defined(USE_OPEN_WEATHER_MAP) int rxStatus = getOWMonecall(client, owm_onecall); if (rxStatus != HTTP_CODE_OK) { @@ -266,6 +261,8 @@ void setup() powerOffDisplay(); beginDeepSleep(startTime, &timeInfo); } + +#if AIR_POLLUTION rxStatus = getOWMairpollution(client, owm_air_pollution); if (rxStatus != HTTP_CODE_OK) { @@ -280,22 +277,45 @@ void setup() powerOffDisplay(); beginDeepSleep(startTime, &timeInfo); } +#endif + +#elif defined(USE_OPEN_METEO) + int rxStatus = getOMCall(client, owm_onecall); + if (rxStatus != HTTP_CODE_OK) + { + killWiFi(); + statusStr = "Open Meteo API"; + tmpStr = String(rxStatus, DEC) + ": " + getHttpResponsePhrase(rxStatus); + initDisplay(); + do + { + drawError(wi_cloud_down_196x196, statusStr, tmpStr); + } while (display.nextPage()); + powerOffDisplay(); + beginDeepSleep(startTime, &timeInfo); + } +#endif + killWiFi(); // WiFi no longer needed // GET INDOOR TEMPERATURE AND HUMIDITY, start BME280... pinMode(PIN_BME_PWR, OUTPUT); digitalWrite(PIN_BME_PWR, HIGH); - float inTemp = NAN; + + float inTemp = NAN; float inHumidity = NAN; + +#if INDOOR + Serial.print(String(TXT_READING_FROM) + " BME280... "); TwoWire I2C_bme = TwoWire(0); Adafruit_BME280 bme; I2C_bme.begin(PIN_BME_SDA, PIN_BME_SCL, 100000); // 100kHz - if(bme.begin(BME_ADDRESS, &I2C_bme)) + if (bme.begin(BME_ADDRESS, &I2C_bme)) { - inTemp = bme.readTemperature(); // Celsius - inHumidity = bme.readHumidity(); // % + inTemp = bme.readTemperature(); // Celsius + inHumidity = bme.readHumidity(); // % // check if BME readings are valid // note: readings are checked again before drawing to screen. If a reading @@ -317,6 +337,7 @@ void setup() Serial.println(statusStr); } digitalWrite(PIN_BME_PWR, LOW); +#endif String refreshTimeStr; getRefreshTimeStr(refreshTimeStr, timeConfigured, &timeInfo); @@ -329,9 +350,13 @@ void setup() { drawCurrentConditions(owm_onecall.current, owm_onecall.daily[0], owm_air_pollution, inTemp, inHumidity); + Serial.println("Drawing current conditions"); drawOutlookGraph(owm_onecall.hourly, owm_onecall.daily, timeInfo); + Serial.println("Drawing outlook graph"); drawForecast(owm_onecall.daily, timeInfo); + Serial.println("Drawing forecast"); drawLocationDate(CITY_STRING, dateStr); + Serial.println("Drawing location and date"); #if DISPLAY_ALERTS drawAlerts(owm_onecall.alerts, CITY_STRING, dateStr); #endif @@ -348,4 +373,3 @@ void setup() void loop() { } // end loop - diff --git a/platformio/src/renderer.cpp b/platformio/src/renderer.cpp index 663765c28..cf33c3a68 100644 --- a/platformio/src/renderer.cpp +++ b/platformio/src/renderer.cpp @@ -38,40 +38,44 @@ #include "icons/icons_196x196.h" #ifdef DISP_BW_V2 - GxEPD2_BW display( - GxEPD2_750_T7(PIN_EPD_CS, - PIN_EPD_DC, - PIN_EPD_RST, - PIN_EPD_BUSY)); +GxEPD2_BW + display( + GxEPD2_750_T7(PIN_EPD_CS, + PIN_EPD_DC, + PIN_EPD_RST, + PIN_EPD_BUSY)); #endif #ifdef DISP_3C_B - GxEPD2_3C display( - GxEPD2_750c_Z08(PIN_EPD_CS, - PIN_EPD_DC, - PIN_EPD_RST, - PIN_EPD_BUSY)); +GxEPD2_3C + display( + GxEPD2_750c_Z08(PIN_EPD_CS, + PIN_EPD_DC, + PIN_EPD_RST, + PIN_EPD_BUSY)); #endif #ifdef DISP_7C_F - GxEPD2_7C display( - GxEPD2_730c_GDEY073D46(PIN_EPD_CS, - PIN_EPD_DC, - PIN_EPD_RST, - PIN_EPD_BUSY)); +GxEPD2_7C + display( + GxEPD2_730c_GDEY073D46(PIN_EPD_CS, + PIN_EPD_DC, + PIN_EPD_RST, + PIN_EPD_BUSY)); #endif #ifdef DISP_BW_V1 - GxEPD2_BW display( - GxEPD2_750(PIN_EPD_CS, - PIN_EPD_DC, - PIN_EPD_RST, - PIN_EPD_BUSY)); +GxEPD2_BW + display( + GxEPD2_750(PIN_EPD_CS, + PIN_EPD_DC, + PIN_EPD_RST, + PIN_EPD_BUSY)); #endif #ifndef ACCENT_COLOR - #define ACCENT_COLOR GxEPD_BLACK +#define ACCENT_COLOR GxEPD_BLACK #endif /* Returns the string width in pixels @@ -135,7 +139,7 @@ void drawMultiLnString(int16_t x, int16_t y, const String &text, // print until we reach max_lines or no more text remains while (current_line < max_lines && !textRemaining.isEmpty()) { - int16_t x1, y1; + int16_t x1, y1; uint16_t w, h; display.getTextBounds(textRemaining, 0, 0, &x1, &y1, &w, &h); @@ -220,6 +224,8 @@ void drawMultiLnString(int16_t x, int16_t y, const String &text, return; } // end drawMultiLnString +// SPIClass hspi(HSPI); + /* Initialize e-paper display */ void initDisplay() @@ -280,41 +286,38 @@ void drawCurrentConditions(const owm_current_t ¤t, #endif #ifdef UNITS_TEMP_CELSIUS dataStr = String(static_cast( - std::round(kelvin_to_celsius(current.temp)))); + std::round(kelvin_to_celsius(current.temp)))); unitStr = TXT_UNITS_TEMP_CELSIUS; #endif #ifdef UNITS_TEMP_FAHRENHEIT dataStr = String(static_cast( - std::round(kelvin_to_fahrenheit(current.temp)))); + std::round(kelvin_to_fahrenheit(current.temp)))); unitStr = TXT_UNITS_TEMP_FAHRENHEIT; #endif // FONT_**_temperature fonts only have the character set used for displaying // temperature (0123456789.-\260) display.setFont(&FONT_48pt8b_temperature); #ifndef DISP_BW_V1 - drawString(196 + 164 / 2 - 20, 196 / 2 + 69 / 2, dataStr, CENTER); + drawString(196 + 164 / 2 - 20, 196 / 2 + 69 / 2, dataStr, CENTER); #elif defined(DISP_BW_V1) - drawString(156 + 164 / 2 - 20, 196 / 2 + 69 / 2, dataStr, CENTER); + drawString(156 + 164 / 2 - 20, 196 / 2 + 69 / 2, dataStr, CENTER); #endif display.setFont(&FONT_14pt8b); drawString(display.getCursorX(), 196 / 2 - 69 / 2 + 20, unitStr, LEFT); // current feels like #ifdef UNITS_TEMP_KELVIN - dataStr = String(TXT_FEELS_LIKE) + ' ' - + String(static_cast(std::round(current.feels_like))); + dataStr = String(TXT_FEELS_LIKE) + ' ' + String(static_cast(std::round(current.feels_like))); #endif #ifdef UNITS_TEMP_CELSIUS - dataStr = String(TXT_FEELS_LIKE) + ' ' - + String(static_cast(std::round( - kelvin_to_celsius(current.feels_like)))) - + '\260'; + // dataStr = String(TXT_FEELS_LIKE) + ' ' + // + String(static_cast(round( + // kelvin_to_celsius(current.feels_like)))) + // + '\xB0'; + dataStr = String(TXT_FEELS_LIKE) + ' ' + String(static_cast(std::round(kelvin_to_celsius(current.feels_like)))) + '\260'; #endif #ifdef UNITS_TEMP_FAHRENHEIT - dataStr = String(TXT_FEELS_LIKE) + ' ' - + String(static_cast(std::round( - kelvin_to_fahrenheit(current.feels_like)))) - + '\260'; + dataStr = String(TXT_FEELS_LIKE) + ' ' + String(static_cast(std::round(kelvin_to_fahrenheit(current.feels_like)))) + '\260'; #endif display.setFont(&FONT_12pt8b); #ifndef DISP_BW_V1 @@ -397,22 +400,22 @@ void drawCurrentConditions(const owm_current_t ¤t, #endif #ifdef UNITS_SPEED_FEETPERSECOND dataStr = String(static_cast(std::round( - meterspersecond_to_feetpersecond(current.wind_speed) ))); + meterspersecond_to_feetpersecond(current.wind_speed)))); unitStr = String(" ") + TXT_UNITS_SPEED_FEETPERSECOND; #endif #ifdef UNITS_SPEED_KILOMETERSPERHOUR dataStr = String(static_cast(std::round( - meterspersecond_to_kilometersperhour(current.wind_speed) ))); + meterspersecond_to_kilometersperhour(current.wind_speed)))); unitStr = String(" ") + TXT_UNITS_SPEED_KILOMETERSPERHOUR; #endif #ifdef UNITS_SPEED_MILESPERHOUR dataStr = String(static_cast(std::round( - meterspersecond_to_milesperhour(current.wind_speed) ))); + meterspersecond_to_milesperhour(current.wind_speed)))); unitStr = String(" ") + TXT_UNITS_SPEED_MILESPERHOUR; #endif #ifdef UNITS_SPEED_KNOTS dataStr = String(static_cast(std::round( - meterspersecond_to_knots(current.wind_speed) ))); + meterspersecond_to_knots(current.wind_speed)))); unitStr = String(" ") + TXT_UNITS_SPEED_KNOTS; #endif #ifdef UNITS_SPEED_BEAUFORT @@ -423,7 +426,7 @@ void drawCurrentConditions(const owm_current_t ¤t, #ifdef WIND_INDICATOR_ARROW drawString(48 + 24, 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, dataStr, LEFT); #else - drawString(48 , 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, dataStr, LEFT); + drawString(48, 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, dataStr, LEFT); #endif display.setFont(&FONT_8pt8b); drawString(display.getCursorX(), 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, @@ -435,10 +438,7 @@ void drawCurrentConditions(const owm_current_t ¤t, drawString(display.getCursorX() + 6, 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, dataStr, LEFT); #endif -#if defined(WIND_INDICATOR_CPN_CARDINAL) \ - || defined(WIND_INDICATOR_CPN_INTERCARDINAL) \ - || defined(WIND_INDICATOR_CPN_SECONDARY_INTERCARDINAL) \ - || defined(WIND_INDICATOR_CPN_TERTIARY_INTERCARDINAL) +#if defined(WIND_INDICATOR_CPN_CARDINAL) || defined(WIND_INDICATOR_CPN_INTERCARDINAL) || defined(WIND_INDICATOR_CPN_SECONDARY_INTERCARDINAL) || defined(WIND_INDICATOR_CPN_TERTIARY_INTERCARDINAL) dataStr = getCompassPointNotation(current.wind_deg); display.setFont(&FONT_12pt8b); drawString(display.getCursorX() + 6, 204 + 17 / 2 + (48 + 8) * 1 + 48 / 2, @@ -452,7 +452,7 @@ void drawCurrentConditions(const owm_current_t ¤t, // uv index display.setFont(&FONT_12pt8b); unsigned int uvi = static_cast( - std::max(std::round(current.uvi), 0.0f)); + std::max(std::round(current.uvi), 0.0f)); dataStr = String(uvi); drawString(48, 204 + 17 / 2 + (48 + 8) * 2 + 48 / 2, dataStr, LEFT); display.setFont(&FONT_7pt8b); @@ -486,7 +486,7 @@ void drawCurrentConditions(const owm_current_t ¤t, const owm_components_t &c = owm_air_pollution.components; // OpenWeatherMap does not provide pb (lead) conentrations, so we pass NULL. int aqi = calc_aqi(AQI_SCALE, c.co, c.nh3, c.no, c.no2, c.o3, NULL, c.so2, - c.pm10, c.pm2_5); + c.pm10, c.pm2_5); int aqi_max = aqi_scale_max(AQI_SCALE); if (aqi > aqi_max) { @@ -534,7 +534,7 @@ void drawCurrentConditions(const owm_current_t ¤t, #endif #ifdef UNITS_TEMP_FAHRENHEIT dataStr = String(static_cast( - std::round(celsius_to_fahrenheit(inTemp)))); + std::round(celsius_to_fahrenheit(inTemp)))); #endif } else @@ -568,41 +568,43 @@ void drawCurrentConditions(const owm_current_t ¤t, #endif #ifdef UNITS_PRES_PASCALS dataStr = String(static_cast(std::round( - hectopascals_to_pascals(current.pressure) ))); + hectopascals_to_pascals(current.pressure)))); unitStr = String(" ") + TXT_UNITS_PRES_PASCALS; #endif #ifdef UNITS_PRES_MILLIMETERSOFMERCURY dataStr = String(static_cast(std::round( - hectopascals_to_millimetersofmercury(current.pressure) ))); + hectopascals_to_millimetersofmercury(current.pressure)))); unitStr = String(" ") + TXT_UNITS_PRES_MILLIMETERSOFMERCURY; #endif #ifdef UNITS_PRES_INCHESOFMERCURY dataStr = String(std::round(1e1f * - hectopascals_to_inchesofmercury(current.pressure) - ) / 1e1f, 1); + hectopascals_to_inchesofmercury(current.pressure)) / + 1e1f, + 1); unitStr = String(" ") + TXT_UNITS_PRES_INCHESOFMERCURY; #endif #ifdef UNITS_PRES_MILLIBARS dataStr = String(static_cast(std::round( - hectopascals_to_millibars(current.pressure) ))); + hectopascals_to_millibars(current.pressure)))); unitStr = String(" ") + TXT_UNITS_PRES_MILLIBARS; #endif #ifdef UNITS_PRES_ATMOSPHERES dataStr = String(std::round(1e3f * - hectopascals_to_atmospheres(current.pressure) ) - / 1e3f, 3); + hectopascals_to_atmospheres(current.pressure)) / + 1e3f, + 3); unitStr = String(" ") + TXT_UNITS_PRES_ATMOSPHERES; #endif #ifdef UNITS_PRES_GRAMSPERSQUARECENTIMETER dataStr = String(static_cast(std::round( - hectopascals_to_gramspersquarecentimeter(current.pressure) - ))); + hectopascals_to_gramspersquarecentimeter(current.pressure)))); unitStr = String(" ") + TXT_UNITS_PRES_GRAMSPERSQUARECENTIMETER; #endif #ifdef UNITS_PRES_POUNDSPERSQUAREINCH dataStr = String(std::round(1e2f * - hectopascals_to_poundspersquareinch(current.pressure) - ) / 1e2f, 2); + hectopascals_to_poundspersquareinch(current.pressure)) / + 1e2f, + 2); unitStr = String(" ") + TXT_UNITS_PRES_POUNDSPERSQUAREINCH; #endif display.setFont(&FONT_12pt8b); @@ -637,92 +639,92 @@ void drawCurrentConditions(const owm_current_t ¤t, { #endif #ifdef UNITS_DIST_MILES - if (vis >= 6) - { + if (vis >= 6) + { #endif - dataStr = "> " + dataStr; - } - drawString(170 + 48, 204 + 17 / 2 + (48 + 8) * 3 + 48 / 2, dataStr, LEFT); - display.setFont(&FONT_8pt8b); - drawString(display.getCursorX(), 204 + 17 / 2 + (48 + 8) * 3 + 48 / 2, - unitStr, LEFT); + dataStr = "> " + dataStr; + } + drawString(170 + 48, 204 + 17 / 2 + (48 + 8) * 3 + 48 / 2, dataStr, LEFT); + display.setFont(&FONT_8pt8b); + drawString(display.getCursorX(), 204 + 17 / 2 + (48 + 8) * 3 + 48 / 2, + unitStr, LEFT); - // indoor humidity - display.setFont(&FONT_12pt8b); - if (!std::isnan(inHumidity)) - { - dataStr = String(static_cast(std::round(inHumidity))); - } - else - { - dataStr = "--"; - } - drawString(170 + 48, 204 + 17 / 2 + (48 + 8) * 4 + 48 / 2, dataStr, LEFT); - display.setFont(&FONT_8pt8b); - drawString(display.getCursorX(), 204 + 17 / 2 + (48 + 8) * 4 + 48 / 2, - "%", LEFT); + // indoor humidity + display.setFont(&FONT_12pt8b); + if (!std::isnan(inHumidity)) + { + dataStr = String(static_cast(std::round(inHumidity))); + } + else + { + dataStr = "--"; + } + drawString(170 + 48, 204 + 17 / 2 + (48 + 8) * 4 + 48 / 2, dataStr, LEFT); + display.setFont(&FONT_8pt8b); + drawString(display.getCursorX(), 204 + 17 / 2 + (48 + 8) * 4 + 48 / 2, + "%", LEFT); #endif // defined(DISP_BW_V2) || defined(DISP_3C_B) || defined(DISP_7C_F) - return; -} // end drawCurrentConditions + return; + } // end drawCurrentConditions -/* This function is responsible for drawing the five day forecast. - */ -void drawForecast(const owm_daily_t *daily, tm timeInfo) -{ - // 5 day, forecast - String hiStr, loStr; - String dataStr, unitStr; - for (int i = 0; i < 5; ++i) + /* This function is responsible for drawing the five day forecast. + */ + void drawForecast(const owm_daily_t *daily, tm timeInfo) { + // 5 day, forecast + String hiStr, loStr; + String dataStr, unitStr; + for (int i = 0; i < 5; ++i) + { #ifndef DISP_BW_V1 - int x = 398 + (i * 82); + int x = 398 + (i * 82); #elif defined(DISP_BW_V1) int x = 318 + (i * 64); #endif - // icons - display.drawInvertedBitmap(x, 98 + 69 / 2 - 32 - 6, - getDailyForecastBitmap64(daily[i]), - 64, 64, GxEPD_BLACK); - // day of week label - display.setFont(&FONT_11pt8b); - char dayBuffer[8] = {}; - _strftime(dayBuffer, sizeof(dayBuffer), "%a", &timeInfo); // abbrv'd day - drawString(x + 31 - 2, 98 + 69 / 2 - 32 - 26 - 6 + 16, dayBuffer, CENTER); - timeInfo.tm_wday = (timeInfo.tm_wday + 1) % 7; // increment to next day - - // high | low - display.setFont(&FONT_8pt8b); - drawString(x + 31, 98 + 69 / 2 + 38 - 6 + 12, "|", CENTER); + // icons + display.drawInvertedBitmap(x, 98 + 69 / 2 - 32 - 6, + getDailyForecastBitmap64(daily[i]), + 64, 64, GxEPD_BLACK); + // day of week label + display.setFont(&FONT_11pt8b); + char dayBuffer[8] = {}; + _strftime(dayBuffer, sizeof(dayBuffer), "%a", &timeInfo); // abbrv'd day + drawString(x + 31 - 2, 98 + 69 / 2 - 32 - 26 - 6 + 16, dayBuffer, CENTER); + timeInfo.tm_wday = (timeInfo.tm_wday + 1) % 7; // increment to next day + + // high | low + display.setFont(&FONT_8pt8b); + drawString(x + 31, 98 + 69 / 2 + 38 - 6 + 12, "|", CENTER); #ifdef UNITS_TEMP_KELVIN - hiStr = String(static_cast(std::round(daily[i].temp.max))); - loStr = String(static_cast(std::round(daily[i].temp.min))); + hiStr = String(static_cast(std::round(daily[i].temp.max))); + loStr = String(static_cast(std::round(daily[i].temp.min))); #endif #ifdef UNITS_TEMP_CELSIUS - hiStr = String(static_cast( - std::round(kelvin_to_celsius(daily[i].temp.max)))) + - "\260"; - loStr = String(static_cast( - std::round(kelvin_to_celsius(daily[i].temp.min)))) + - "\260"; + hiStr = String(static_cast( + std::round(kelvin_to_celsius(daily[i].temp.max)))) + + "\260"; + loStr = String(static_cast( + std::round(kelvin_to_celsius(daily[i].temp.min)))) + + "\260"; #endif #ifdef UNITS_TEMP_FAHRENHEIT - hiStr = String(static_cast( - std::round(kelvin_to_fahrenheit(daily[i].temp.max)))) + - "\260"; - loStr = String(static_cast( - std::round(kelvin_to_fahrenheit(daily[i].temp.min)))) + - "\260"; + hiStr = String(static_cast( + std::round(kelvin_to_fahrenheit(daily[i].temp.max)))) + + "\260"; + loStr = String(static_cast( + std::round(kelvin_to_fahrenheit(daily[i].temp.min)))) + + "\260"; #endif - drawString(x + 31 - 4, 98 + 69 / 2 + 38 - 6 + 12, hiStr, RIGHT); - drawString(x + 31 + 5, 98 + 69 / 2 + 38 - 6 + 12, loStr, LEFT); + drawString(x + 31 - 4, 98 + 69 / 2 + 38 - 6 + 12, hiStr, RIGHT); + drawString(x + 31 + 5, 98 + 69 / 2 + 38 - 6 + 12, loStr, LEFT); // daily forecast precipitation #if DISPLAY_DAILY_PRECIP - float dailyPrecip; + float dailyPrecip; #if defined(UNITS_DAILY_PRECIP_POP) - dailyPrecip = daily[i].pop * 100; - dataStr = String(static_cast(dailyPrecip)); - unitStr = "%"; + dailyPrecip = daily[i].pop * 100; + dataStr = String(static_cast(dailyPrecip)); + unitStr = "%"; #else dailyPrecip = daily[i].snow + daily[i].rain; #if defined(UNITS_DAILY_PRECIP_MILLIMETERS) @@ -767,243 +769,241 @@ void drawForecast(const owm_daily_t *daily, tm timeInfo) const String &city, const String &date) { #if DEBUG_LEVEL >= 1 - Serial.println("[debug] alerts.size() : " + String(alerts.size())); + Serial.println("[debug] alerts.size() : " + String(alerts.size())); #endif - if (alerts.size() == 0) - { // no alerts to draw - return; - } + if (alerts.size() == 0) + { // no alerts to draw + return; + } - int *ignore_list = (int *) calloc(alerts.size(), sizeof(*ignore_list)); - int *alert_indices = (int *) calloc(alerts.size(), sizeof(*alert_indices)); - if (!ignore_list || !alert_indices) - { - Serial.println("Error: Failed to allocate memory while handling alerts."); - free(ignore_list); - free(alert_indices); - return; - } + int *ignore_list = (int *)calloc(alerts.size(), sizeof(*ignore_list)); + int *alert_indices = (int *)calloc(alerts.size(), sizeof(*alert_indices)); + if (!ignore_list || !alert_indices) + { + Serial.println("Error: Failed to allocate memory while handling alerts."); + free(ignore_list); + free(alert_indices); + return; + } - // Converts all event text and tags to lowercase, removes extra information, - // and filters out redundant alerts of lesser urgency. - filterAlerts(alerts, ignore_list); + // Converts all event text and tags to lowercase, removes extra information, + // and filters out redundant alerts of lesser urgency. + filterAlerts(alerts, ignore_list); - // limit alert text width so that is does not run into the location or date - // strings - display.setFont(&FONT_16pt8b); - int city_w = getStringWidth(city); - display.setFont(&FONT_12pt8b); - int date_w = getStringWidth(date); - int max_w = DISP_WIDTH - 2 - std::max(city_w, date_w) - (196 + 4) - 8; + // limit alert text width so that is does not run into the location or date + // strings + display.setFont(&FONT_16pt8b); + int city_w = getStringWidth(city); + display.setFont(&FONT_12pt8b); + int date_w = getStringWidth(date); + int max_w = DISP_WIDTH - 2 - std::max(city_w, date_w) - (196 + 4) - 8; - // find indices of valid alerts - int num_valid_alerts = 0; + // find indices of valid alerts + int num_valid_alerts = 0; #if DEBUG_LEVEL >= 1 - Serial.print("[debug] ignore_list : [ "); + Serial.print("[debug] ignore_list : [ "); #endif - for (int i = 0; i < alerts.size(); ++i) - { + for (int i = 0; i < alerts.size(); ++i) + { #if DEBUG_LEVEL >= 1 - Serial.print(String(ignore_list[i]) + " "); + Serial.print(String(ignore_list[i]) + " "); #endif - if (!ignore_list[i]) - { - alert_indices[num_valid_alerts] = i; - ++num_valid_alerts; + if (!ignore_list[i]) + { + alert_indices[num_valid_alerts] = i; + ++num_valid_alerts; + } } - } #if DEBUG_LEVEL >= 1 - Serial.println("]\n[debug] num_valid_alerts : " + String(num_valid_alerts)); + Serial.println("]\n[debug] num_valid_alerts : " + String(num_valid_alerts)); #endif - if (num_valid_alerts == 1) - { // 1 alert - // adjust max width to for 48x48 icons - max_w -= 48; + if (num_valid_alerts == 1) + { // 1 alert + // adjust max width to for 48x48 icons + max_w -= 48; - owm_alerts_t &cur_alert = alerts[alert_indices[0]]; - display.drawInvertedBitmap(196, 8, getAlertBitmap48(cur_alert), 48, 48, - ACCENT_COLOR); - // must be called after getAlertBitmap - toTitleCase(cur_alert.event); + owm_alerts_t &cur_alert = alerts[alert_indices[0]]; + display.drawInvertedBitmap(196, 8, getAlertBitmap48(cur_alert), 48, 48, + ACCENT_COLOR); + // must be called after getAlertBitmap + toTitleCase(cur_alert.event); - display.setFont(&FONT_14pt8b); - if (getStringWidth(cur_alert.event) <= max_w) - { // Fits on a single line, draw along bottom - drawString(196 + 48 + 4, 24 + 8 - 12 + 20 + 1, cur_alert.event, LEFT); - } - else - { // use smaller font - display.setFont(&FONT_12pt8b); + display.setFont(&FONT_14pt8b); if (getStringWidth(cur_alert.event) <= max_w) - { // Fits on a single line with smaller font, draw along bottom - drawString(196 + 48 + 4, 24 + 8 - 12 + 17 + 1, cur_alert.event, LEFT); + { // Fits on a single line, draw along bottom + drawString(196 + 48 + 4, 24 + 8 - 12 + 20 + 1, cur_alert.event, LEFT); } else - { // Does not fit on a single line, draw higher to allow room for 2nd line - drawMultiLnString(196 + 48 + 4, 24 + 8 - 12 + 17 - 11, - cur_alert.event, LEFT, max_w, 2, 23); + { // use smaller font + display.setFont(&FONT_12pt8b); + if (getStringWidth(cur_alert.event) <= max_w) + { // Fits on a single line with smaller font, draw along bottom + drawString(196 + 48 + 4, 24 + 8 - 12 + 17 + 1, cur_alert.event, LEFT); + } + else + { // Does not fit on a single line, draw higher to allow room for 2nd line + drawMultiLnString(196 + 48 + 4, 24 + 8 - 12 + 17 - 11, + cur_alert.event, LEFT, max_w, 2, 23); + } } - } - } // end 1 alert - else - { // 2 alerts - // adjust max width to for 32x32 icons - max_w -= 32; + } // end 1 alert + else + { // 2 alerts + // adjust max width to for 32x32 icons + max_w -= 32; - display.setFont(&FONT_12pt8b); - for (int i = 0; i < 2; ++i) - { - owm_alerts_t &cur_alert = alerts[alert_indices[i]]; + display.setFont(&FONT_12pt8b); + for (int i = 0; i < 2; ++i) + { + owm_alerts_t &cur_alert = alerts[alert_indices[i]]; - display.drawInvertedBitmap(196, (i * 32), getAlertBitmap32(cur_alert), - 32, 32, ACCENT_COLOR); - // must be called after getAlertBitmap - toTitleCase(cur_alert.event); + display.drawInvertedBitmap(196, (i * 32), getAlertBitmap32(cur_alert), + 32, 32, ACCENT_COLOR); + // must be called after getAlertBitmap + toTitleCase(cur_alert.event); - drawMultiLnString(196 + 32 + 3, 5 + 17 + (i * 32), - cur_alert.event, LEFT, max_w, 1, 0); - } // end for-loop - } // end 2 alerts + drawMultiLnString(196 + 32 + 3, 5 + 17 + (i * 32), + cur_alert.event, LEFT, max_w, 1, 0); + } // end for-loop + } // end 2 alerts - free(ignore_list); - free(alert_indices); + free(ignore_list); + free(alert_indices); - return; -} // end drawAlerts + return; + } // end drawAlerts -/* This function is responsible for drawing the city string and date - * information in the top right corner. - */ -void drawLocationDate(const String &city, const String &date) -{ - // location, date - display.setFont(&FONT_16pt8b); - drawString(DISP_WIDTH - 2, 23, city, RIGHT, ACCENT_COLOR); - display.setFont(&FONT_12pt8b); - drawString(DISP_WIDTH - 2, 30 + 4 + 17, date, RIGHT); - return; -} // end drawLocationDate + /* This function is responsible for drawing the city string and date + * information in the top right corner. + */ + void drawLocationDate(const String &city, const String &date) + { + // location, date + display.setFont(&FONT_16pt8b); + drawString(DISP_WIDTH - 2, 23, city, RIGHT, ACCENT_COLOR); + display.setFont(&FONT_12pt8b); + drawString(DISP_WIDTH - 2, 30 + 4 + 17, date, RIGHT); + return; + } // end drawLocationDate -/* The % operator in C++ is not a true modulo operator but it instead a - * remainder operator. The remainder operator and modulo operator are equivalent - * for positive numbers, but not for negatives. The follow implementation of the - * modulo operator works for +/-a and +b. - */ -inline int modulo(int a, int b) -{ - const int result = a % b; - return result >= 0 ? result : result + b; -} + /* The % operator in C++ is not a true modulo operator but it instead a + * remainder operator. The remainder operator and modulo operator are equivalent + * for positive numbers, but not for negatives. The follow implementation of the + * modulo operator works for +/-a and +b. + */ + inline int modulo(int a, int b) + { + const int result = a % b; + return result >= 0 ? result : result + b; + } -/* Convert temperature in kelvin to the display y coordinate to be plotted. - */ -int kelvin_to_plot_y(float kelvin, int tempBoundMin, float yPxPerUnit, - int yBoundMin) -{ + /* Convert temperature in kelvin to the display y coordinate to be plotted. + */ + int kelvin_to_plot_y(float kelvin, int tempBoundMin, float yPxPerUnit, + int yBoundMin) + { #ifdef UNITS_TEMP_KELVIN - return static_cast(std::round( - yBoundMin - (yPxPerUnit * (kelvin - tempBoundMin)) )); + return static_cast(std::round( + yBoundMin - (yPxPerUnit * (kelvin - tempBoundMin)))); #endif #ifdef UNITS_TEMP_CELSIUS - return static_cast(std::round( - yBoundMin - (yPxPerUnit * (kelvin_to_celsius(kelvin) - tempBoundMin)) )); + return static_cast(std::round( + yBoundMin - (yPxPerUnit * (kelvin_to_celsius(kelvin) - tempBoundMin)))); #endif #ifdef UNITS_TEMP_FAHRENHEIT - return static_cast(std::round( - yBoundMin - (yPxPerUnit * (kelvin_to_fahrenheit(kelvin) - tempBoundMin)) )); + return static_cast(std::round( + yBoundMin - (yPxPerUnit * (kelvin_to_fahrenheit(kelvin) - tempBoundMin)))); #endif -} + } -/* This function is responsible for drawing the outlook graph for the specified - * number of hours(up to 48). - */ -void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, - tm timeInfo) -{ - const int xPos0 = 350; - int xPos1 = DISP_WIDTH; - const int yPos0 = 216; - const int yPos1 = DISP_HEIGHT - 46; + /* This function is responsible for drawing the outlook graph for the specified + * number of hours(up to 48). + */ + void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, + tm timeInfo) + { + const int xPos0 = 350; + int xPos1 = DISP_WIDTH; + const int yPos0 = 216; + const int yPos1 = DISP_HEIGHT - 46; - // calculate y max/min and intervals - int yMajorTicks = 5; + // calculate y max/min and intervals + int yMajorTicks = 5; #ifdef UNITS_TEMP_KELVIN - float tempMin = hourly[0].temp; + float tempMin = hourly[0].temp; #endif #ifdef UNITS_TEMP_CELSIUS - float tempMin = kelvin_to_celsius(hourly[0].temp); + // float tempMin = kelvin_to_celsius(hourly[0].temp); + float tempMin = hourly[0].temp; #endif #ifdef UNITS_TEMP_FAHRENHEIT - float tempMin = kelvin_to_fahrenheit(hourly[0].temp); + float tempMin = kelvin_to_fahrenheit(hourly[0].temp); #endif - float tempMax = tempMin; + float tempMax = tempMin; #ifdef UNITS_HOURLY_PRECIP_POP - float precipMax = hourly[0].pop; + float precipMax = hourly[0].pop; #else float precipMax = hourly[0].rain_1h + hourly[0].snow_1h; #endif - int yTempMajorTicks = 5; - float newTemp = 0; - for (int i = 1; i < HOURLY_GRAPH_MAX; ++i) - { + int yTempMajorTicks = 5; + float newTemp = 0; + for (int i = 1; i < HOURLY_GRAPH_MAX; ++i) + { #ifdef UNITS_TEMP_KELVIN - newTemp = hourly[i].temp; + newTemp = hourly[i].temp; #endif #ifdef UNITS_TEMP_CELSIUS - newTemp = kelvin_to_celsius(hourly[i].temp); + // newTemp = kelvin_to_celsius(hourly[i].temp); + newTemp = hourly[i].temp; #endif #ifdef UNITS_TEMP_FAHRENHEIT - newTemp = kelvin_to_fahrenheit(hourly[i].temp); + newTemp = kelvin_to_fahrenheit(hourly[i].temp); #endif - tempMin = std::min(tempMin, newTemp); - tempMax = std::max(tempMax, newTemp); + tempMin = std::min(tempMin, newTemp); + tempMax = std::max(tempMax, newTemp); #ifdef UNITS_HOURLY_PRECIP_POP - precipMax = std::max(precipMax, hourly[i].pop); + precipMax = std::max(precipMax, hourly[i].pop); #else precipMax = std::max( - precipMax, hourly[i].rain_1h + hourly[i].snow_1h); + precipMax, hourly[i].rain_1h + hourly[i].snow_1h); #endif - } - int tempBoundMin = static_cast(tempMin - 1) - - modulo(static_cast(tempMin - 1), yTempMajorTicks); - int tempBoundMax = static_cast(tempMax + 1) - + (yTempMajorTicks - modulo(static_cast(tempMax + 1), yTempMajorTicks)); + } + int tempBoundMin = static_cast(tempMin - 1) - modulo(static_cast(tempMin - 1), yTempMajorTicks); + int tempBoundMax = static_cast(tempMax + 1) + (yTempMajorTicks - modulo(static_cast(tempMax + 1), yTempMajorTicks)); - // while we have to many major ticks then increase the step - while ((tempBoundMax - tempBoundMin) / yTempMajorTicks > yMajorTicks) - { - yTempMajorTicks += 5; - tempBoundMin = static_cast(tempMin - 1) - - modulo(static_cast(tempMin - 1), yTempMajorTicks); - tempBoundMax = static_cast(tempMax + 1) + (yTempMajorTicks - - modulo(static_cast(tempMax + 1), yTempMajorTicks)); - } - // while we have not enough major ticks, add to either bound - while ((tempBoundMax - tempBoundMin) / yTempMajorTicks < yMajorTicks) - { - // add to whatever bound is closer to the actual min/max - if (tempMin - tempBoundMin <= tempBoundMax - tempMax) + // while we have to many major ticks then increase the step + while ((tempBoundMax - tempBoundMin) / yTempMajorTicks > yMajorTicks) { - tempBoundMin -= yTempMajorTicks; + yTempMajorTicks += 5; + tempBoundMin = static_cast(tempMin - 1) - modulo(static_cast(tempMin - 1), yTempMajorTicks); + tempBoundMax = static_cast(tempMax + 1) + (yTempMajorTicks - modulo(static_cast(tempMax + 1), yTempMajorTicks)); } - else + // while we have not enough major ticks, add to either bound + while ((tempBoundMax - tempBoundMin) / yTempMajorTicks < yMajorTicks) { - tempBoundMax += yTempMajorTicks; + // add to whatever bound is closer to the actual min/max + if (tempMin - tempBoundMin <= tempBoundMax - tempMax) + { + tempBoundMin -= yTempMajorTicks; + } + else + { + tempBoundMax += yTempMajorTicks; + } } - } #ifdef UNITS_HOURLY_PRECIP_POP - xPos1 = DISP_WIDTH - 23; - float precipBoundMax; - if (precipMax > 0) - { - precipBoundMax = 100.0f; - } - else - { - precipBoundMax = 0.0f; - } + xPos1 = DISP_WIDTH - 23; + float precipBoundMax; + if (precipMax > 0) + { + precipBoundMax = 100.0f; + } + else + { + precipBoundMax = 0.0f; + } #else #ifdef UNITS_HOURLY_PRECIP_MILLIMETERS xPos1 = DISP_WIDTH - 24; @@ -1056,40 +1056,39 @@ void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, float precipRoundingMultiplier = std::pow(10.f, yPrecipMajorTickDecimals); #endif - if (precipBoundMax > 0) - { // fill need extra room for labels - xPos1 -= 23; - } + if (precipBoundMax > 0) + { // fill need extra room for labels + xPos1 -= 23; + } - // draw x axis - display.drawLine(xPos0, yPos1 , xPos1, yPos1 , GxEPD_BLACK); - display.drawLine(xPos0, yPos1 - 1, xPos1, yPos1 - 1, GxEPD_BLACK); + // draw x axis + display.drawLine(xPos0, yPos1, xPos1, yPos1, GxEPD_BLACK); + display.drawLine(xPos0, yPos1 - 1, xPos1, yPos1 - 1, GxEPD_BLACK); - // draw y axis - float yInterval = (yPos1 - yPos0) / static_cast(yMajorTicks); - for (int i = 0; i <= yMajorTicks; ++i) - { - String dataStr; - int yTick = static_cast(yPos0 + (i * yInterval)); - display.setFont(&FONT_8pt8b); - // Temperature - dataStr = String(tempBoundMax - (i * yTempMajorTicks)); + // draw y axis + float yInterval = (yPos1 - yPos0) / static_cast(yMajorTicks); + for (int i = 0; i <= yMajorTicks; ++i) + { + String dataStr; + int yTick = static_cast(yPos0 + (i * yInterval)); + display.setFont(&FONT_8pt8b); + // Temperature + dataStr = String(tempBoundMax - (i * yTempMajorTicks)); #if defined(UNITS_TEMP_CELSIUS) || defined(UNITS_TEMP_FAHRENHEIT) - dataStr += "\260"; + dataStr += "\260"; #endif - drawString(xPos0 - 8, yTick + 4, dataStr, RIGHT, ACCENT_COLOR); + drawString(xPos0 - 8, yTick + 4, dataStr, RIGHT, ACCENT_COLOR); - if (precipBoundMax > 0) - { // don't labels if precip is 0 + if (precipBoundMax > 0) + { // don't labels if precip is 0 #ifdef UNITS_HOURLY_PRECIP_POP - // PoP - dataStr = String(100 - (i * 20)); - String precipUnit = "%"; + // PoP + dataStr = String(100 - (i * 20)); + String precipUnit = "%"; #else // Precipitation volume float precipTick = precipBoundMax - (i * yPrecipMajorTickValue); - precipTick = std::round(precipTick * precipRoundingMultiplier) - / precipRoundingMultiplier; + precipTick = std::round(precipTick * precipRoundingMultiplier) / precipRoundingMultiplier; dataStr = String(precipTick, yPrecipMajorTickDecimals); #ifdef UNITS_HOURLY_PRECIP_MILLIMETERS String precipUnit = String(" ") + TXT_UNITS_PRECIP_MILLIMETERS; @@ -1102,103 +1101,101 @@ void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, #endif #endif - drawString(xPos1 + 8, yTick + 4, dataStr, LEFT); - display.setFont(&FONT_5pt8b); - drawString(display.getCursorX(), yTick + 4, precipUnit, LEFT); - } // end draw labels if precip is >0 + drawString(xPos1 + 8, yTick + 4, dataStr, LEFT); + display.setFont(&FONT_5pt8b); + drawString(display.getCursorX(), yTick + 4, precipUnit, LEFT); + } // end draw labels if precip is >0 - // draw dotted line - if (i < yMajorTicks) - { - for (int x = xPos0; x <= xPos1 + 1; x += 3) + // draw dotted line + if (i < yMajorTicks) { - display.drawPixel(x, yTick + (yTick % 2), GxEPD_BLACK); + for (int x = xPos0; x <= xPos1 + 1; x += 3) + { + display.drawPixel(x, yTick + (yTick % 2), GxEPD_BLACK); + } } } - } - int xMaxTicks = 8; - int hourInterval = static_cast(ceil(HOURLY_GRAPH_MAX - / static_cast(xMaxTicks))); - float xInterval = (xPos1 - xPos0 - 1) / static_cast(HOURLY_GRAPH_MAX); - display.setFont(&FONT_8pt8b); - - // precalculate all x and y coordinates for temperature values - float yPxPerUnit = (yPos1 - yPos0) - / static_cast(tempBoundMax - tempBoundMin); - std::vector x_t; - std::vector y_t; - x_t.reserve(HOURLY_GRAPH_MAX); - y_t.reserve(HOURLY_GRAPH_MAX); + int xMaxTicks = 8; + int hourInterval = static_cast(ceil(HOURLY_GRAPH_MAX / static_cast(xMaxTicks))); + float xInterval = (xPos1 - xPos0 - 1) / static_cast(HOURLY_GRAPH_MAX); + display.setFont(&FONT_8pt8b); + + // precalculate all x and y coordinates for temperature values + float yPxPerUnit = (yPos1 - yPos0) / static_cast(tempBoundMax - tempBoundMin); + std::vector x_t; + std::vector y_t; + x_t.reserve(HOURLY_GRAPH_MAX); + y_t.reserve(HOURLY_GRAPH_MAX); for (int i = 0; i < HOURLY_GRAPH_MAX; ++i) - { - y_t[i] = kelvin_to_plot_y(hourly[i].temp, tempBoundMin, yPxPerUnit, yPos1); - x_t[i] = static_cast(std::round(xPos0 + (i * xInterval) - + (0.5 * xInterval) )); - } + { + y_t[i] = kelvin_to_plot_y(hourly[i].temp, tempBoundMin, yPxPerUnit, yPos1); + x_t[i] = static_cast(std::round(xPos0 + (i * xInterval) + (0.5 * xInterval))); + } #if DISPLAY_HOURLY_ICONS - int day_idx = 0; + int day_idx = 0; #endif - display.setFont(&FONT_8pt8b); - for (int i = 0; i < HOURLY_GRAPH_MAX; ++i) - { - int xTick = static_cast(xPos0 + (i * xInterval)); - int x0_t, x1_t, y0_t, y1_t; - - if (i > 0) + display.setFont(&FONT_8pt8b); + for (int i = 0; i < HOURLY_GRAPH_MAX; ++i) { - // temperature - x0_t = x_t[i - 1]; - x1_t = x_t[i ]; - y0_t = y_t[i - 1]; - y1_t = y_t[i ]; - // graph temperature - display.drawLine(x0_t , y0_t , x1_t , y1_t , ACCENT_COLOR); - display.drawLine(x0_t , y0_t + 1, x1_t , y1_t + 1, ACCENT_COLOR); - display.drawLine(x0_t - 1, y0_t , x1_t - 1, y1_t , ACCENT_COLOR); - - // draw hourly bitmap -#if DISPLAY_HOURLY_ICONS - if (daily[day_idx].dt + 86400 <= hourly[i].dt) { - ++day_idx; - } - if ((i % hourInterval) == 0) // skip first and last tick + int xTick = static_cast(xPos0 + (i * xInterval)); + int x0_t, x1_t, y0_t, y1_t; + + if (i > 0) { - int y_b = INT_MAX; - // find the highest (lowest in coordinate value) temperature point that - // exists within the width of the icon. - // find closest point above the temperature line where the icon won't - // interect the temperature line. - // y = mx + b - int span = static_cast(std::round(16 / xInterval)); - int l_idx = std::max(i - 1 - span, 0); - int r_idx = std::min(i + span, HOURLY_GRAPH_MAX - 1); - // left intersecting slope - float m_l = (y_t[l_idx + 1] - y_t[l_idx]) / xInterval; - int x_l = xTick - 16 - x_t[l_idx]; - int y_l = static_cast(std::round(m_l * x_l + y_t[l_idx])); - y_b = std::min(y_l, y_b); - // right intersecting slope - float m_r = (y_t[r_idx] - y_t[r_idx - 1]) / xInterval; - int x_r = xTick + 16 - x_t[r_idx - 1]; - int y_r = static_cast(std::round(m_r * x_r + y_t[r_idx - 1])); - y_b = std::min(y_r, y_b); - // any peaks in between - for (int idx = l_idx + 1; idx < r_idx; ++idx) + // temperature + x0_t = x_t[i - 1]; + x1_t = x_t[i]; + y0_t = y_t[i - 1]; + y1_t = y_t[i]; + // graph temperature + display.drawLine(x0_t, y0_t, x1_t, y1_t, ACCENT_COLOR); + display.drawLine(x0_t, y0_t + 1, x1_t, y1_t + 1, ACCENT_COLOR); + display.drawLine(x0_t - 1, y0_t, x1_t - 1, y1_t, ACCENT_COLOR); + + // draw hourly bitmap +#if DISPLAY_HOURLY_ICONS + if (daily[day_idx].dt + 86400 <= hourly[i].dt) { - y_b = std::min(y_t[idx], y_b); + ++day_idx; + } + if ((i % hourInterval) == 0) // skip first and last tick + { + int y_b = INT_MAX; + // find the highest (lowest in coordinate value) temperature point that + // exists within the width of the icon. + // find closest point above the temperature line where the icon won't + // interect the temperature line. + // y = mx + b + int span = static_cast(std::round(16 / xInterval)); + int l_idx = std::max(i - 1 - span, 0); + int r_idx = std::min(i + span, HOURLY_GRAPH_MAX - 1); + // left intersecting slope + float m_l = (y_t[l_idx + 1] - y_t[l_idx]) / xInterval; + int x_l = xTick - 16 - x_t[l_idx]; + int y_l = static_cast(std::round(m_l * x_l + y_t[l_idx])); + y_b = std::min(y_l, y_b); + // right intersecting slope + float m_r = (y_t[r_idx] - y_t[r_idx - 1]) / xInterval; + int x_r = xTick + 16 - x_t[r_idx - 1]; + int y_r = static_cast(std::round(m_r * x_r + y_t[r_idx - 1])); + y_b = std::min(y_r, y_b); + // any peaks in between + for (int idx = l_idx + 1; idx < r_idx; ++idx) + { + y_b = std::min(y_t[idx], y_b); + } + const uint8_t *bitmap = getHourlyForecastBitmap32(hourly[i], + daily[day_idx]); + display.drawInvertedBitmap(xTick - 16, y_b - 32, + bitmap, 32, 32, GxEPD_BLACK); } - const uint8_t *bitmap = getHourlyForecastBitmap32(hourly[i], - daily[day_idx]); - display.drawInvertedBitmap(xTick - 16, y_b - 32, - bitmap, 32, 32, GxEPD_BLACK); - } #endif - } + } #ifdef UNITS_HOURLY_PRECIP_POP - float precipVal = hourly[i].pop * 100; + float precipVal = hourly[i].pop * 100; #else float precipVal = hourly[i].rain_1h + hourly[i].snow_1h; #ifdef UNITS_HOURLY_PRECIP_CENTIMETERS @@ -1209,153 +1206,151 @@ void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, #endif #endif - x0_t = static_cast(std::round( xPos0 + 1 + (i * xInterval))); - x1_t = static_cast(std::round( xPos0 + 1 + ((i + 1) * xInterval) )); - yPxPerUnit = (yPos1 - yPos0) / precipBoundMax; - y0_t = static_cast(std::round( yPos1 - (yPxPerUnit * (precipVal)) )); - y1_t = yPos1; + x0_t = static_cast(std::round(xPos0 + 1 + (i * xInterval))); + x1_t = static_cast(std::round(xPos0 + 1 + ((i + 1) * xInterval))); + yPxPerUnit = (yPos1 - yPos0) / precipBoundMax; + y0_t = static_cast(std::round(yPos1 - (yPxPerUnit * (precipVal)))); + y1_t = yPos1; - // graph Precipitation - for (int y = y1_t - 1; y > y0_t; y -= 2) - { - for (int x = x0_t + (x0_t % 2); x < x1_t; x += 2) + // graph Precipitation + for (int y = y1_t - 1; y > y0_t; y -= 2) { - display.drawPixel(x, y, GxEPD_BLACK); + for (int x = x0_t + (x0_t % 2); x < x1_t; x += 2) + { + display.drawPixel(x, y, GxEPD_BLACK); + } + } + + if ((i % hourInterval) == 0) + { + // draw x tick marks + display.drawLine(xTick, yPos1 + 1, xTick, yPos1 + 4, GxEPD_BLACK); + display.drawLine(xTick + 1, yPos1 + 1, xTick + 1, yPos1 + 4, GxEPD_BLACK); + // draw x axis labels + char timeBuffer[12] = {}; // big enough to accommodate "hh:mm:ss am" + time_t ts = hourly[i].dt; + tm *timeInfo = localtime(&ts); + _strftime(timeBuffer, sizeof(timeBuffer), HOUR_FORMAT, timeInfo); + drawString(xTick, yPos1 + 1 + 12 + 4 + 3, timeBuffer, CENTER); } } - if ((i % hourInterval) == 0) + // draw the last tick mark + if ((HOURLY_GRAPH_MAX % hourInterval) == 0) { + int xTick = static_cast( + std::round(xPos0 + (HOURLY_GRAPH_MAX * xInterval))); // draw x tick marks - display.drawLine(xTick , yPos1 + 1, xTick , yPos1 + 4, GxEPD_BLACK); + display.drawLine(xTick, yPos1 + 1, xTick, yPos1 + 4, GxEPD_BLACK); display.drawLine(xTick + 1, yPos1 + 1, xTick + 1, yPos1 + 4, GxEPD_BLACK); // draw x axis labels char timeBuffer[12] = {}; // big enough to accommodate "hh:mm:ss am" - time_t ts = hourly[i].dt; + time_t ts = hourly[HOURLY_GRAPH_MAX - 1].dt + 3600; tm *timeInfo = localtime(&ts); _strftime(timeBuffer, sizeof(timeBuffer), HOUR_FORMAT, timeInfo); drawString(xTick, yPos1 + 1 + 12 + 4 + 3, timeBuffer, CENTER); } - } + return; + } // end drawOutlookGraph - // draw the last tick mark - if ((HOURLY_GRAPH_MAX % hourInterval) == 0) + /* This function is responsible for drawing the status bar along the bottom of + * the display. + */ + void drawStatusBar(const String &statusStr, const String &refreshTimeStr, + int rssi, uint32_t batVoltage) { - int xTick = static_cast( - std::round(xPos0 + (HOURLY_GRAPH_MAX * xInterval))); - // draw x tick marks - display.drawLine(xTick , yPos1 + 1, xTick , yPos1 + 4, GxEPD_BLACK); - display.drawLine(xTick + 1, yPos1 + 1, xTick + 1, yPos1 + 4, GxEPD_BLACK); - // draw x axis labels - char timeBuffer[12] = {}; // big enough to accommodate "hh:mm:ss am" - time_t ts = hourly[HOURLY_GRAPH_MAX - 1].dt + 3600; - tm *timeInfo = localtime(&ts); - _strftime(timeBuffer, sizeof(timeBuffer), HOUR_FORMAT, timeInfo); - drawString(xTick, yPos1 + 1 + 12 + 4 + 3, timeBuffer, CENTER); - } - - return; -} // end drawOutlookGraph - -/* This function is responsible for drawing the status bar along the bottom of - * the display. - */ -void drawStatusBar(const String &statusStr, const String &refreshTimeStr, - int rssi, uint32_t batVoltage) -{ - String dataStr; - uint16_t dataColor = GxEPD_BLACK; - display.setFont(&FONT_6pt8b); - int pos = DISP_WIDTH - 2; - const int sp = 2; + String dataStr; + uint16_t dataColor = GxEPD_BLACK; + display.setFont(&FONT_6pt8b); + int pos = DISP_WIDTH - 2; + const int sp = 2; #if BATTERY_MONITORING - // battery - (expecting 3.7v LiPo) - uint32_t batPercent = calcBatPercent(batVoltage, - MIN_BATTERY_VOLTAGE, - MAX_BATTERY_VOLTAGE); + // battery - (expecting 3.7v LiPo) + uint32_t batPercent = calcBatPercent(batVoltage, + MIN_BATTERY_VOLTAGE, + MAX_BATTERY_VOLTAGE); #if defined(DISP_3C_B) || defined(DISP_7C_F) - if (batVoltage < WARN_BATTERY_VOLTAGE) - { - dataColor = ACCENT_COLOR; - } + if (batVoltage < WARN_BATTERY_VOLTAGE) + { + dataColor = ACCENT_COLOR; + } #endif - dataStr = String(batPercent) + "%"; + dataStr = String(batPercent) + "%"; #if STATUS_BAR_EXTRAS_BAT_VOLTAGE - dataStr += " (" + String( std::round(batVoltage / 10.f) / 100.f, 2 ) + "v)"; + dataStr += " (" + String(std::round(batVoltage / 10.f) / 100.f, 2) + "v)"; #endif - drawString(pos, DISP_HEIGHT - 1 - 2, dataStr, RIGHT, dataColor); - pos -= getStringWidth(dataStr) + 25; - display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 17, - getBatBitmap24(batPercent), 24, 24, dataColor); - pos -= sp + 9; + drawString(pos, DISP_HEIGHT - 1 - 2, dataStr, RIGHT, dataColor); + pos -= getStringWidth(dataStr) + 25; + display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 17, + getBatBitmap24(batPercent), 24, 24, dataColor); + pos -= sp + 9; #endif - // WiFi - dataStr = String(getWiFidesc(rssi)); - dataColor = rssi >= -70 ? GxEPD_BLACK : ACCENT_COLOR; + // WiFi + dataStr = String(getWiFidesc(rssi)); + dataColor = rssi >= -70 ? GxEPD_BLACK : ACCENT_COLOR; #if STATUS_BAR_EXTRAS_WIFI_RSSI - if (rssi != 0) - { - dataStr += " (" + String(rssi) + "dBm)"; - } + if (rssi != 0) + { + dataStr += " (" + String(rssi) + "dBm)"; + } #endif - drawString(pos, DISP_HEIGHT - 1 - 2, dataStr, RIGHT, dataColor); - pos -= getStringWidth(dataStr) + 19; - display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 13, getWiFiBitmap16(rssi), - 16, 16, dataColor); - pos -= sp + 8; - - // last refresh - dataColor = GxEPD_BLACK; - drawString(pos, DISP_HEIGHT - 1 - 2, refreshTimeStr, RIGHT, dataColor); - pos -= getStringWidth(refreshTimeStr) + 25; - display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 21, wi_refresh_32x32, - 32, 32, dataColor); - pos -= sp; - - // status - dataColor = ACCENT_COLOR; - if (!statusStr.isEmpty()) - { - drawString(pos, DISP_HEIGHT - 1 - 2, statusStr, RIGHT, dataColor); - pos -= getStringWidth(statusStr) + 24; - display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 18, error_icon_24x24, - 24, 24, dataColor); - } + drawString(pos, DISP_HEIGHT - 1 - 2, dataStr, RIGHT, dataColor); + pos -= getStringWidth(dataStr) + 19; + display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 13, getWiFiBitmap16(rssi), + 16, 16, dataColor); + pos -= sp + 8; + + // last refresh + dataColor = GxEPD_BLACK; + drawString(pos, DISP_HEIGHT - 1 - 2, refreshTimeStr, RIGHT, dataColor); + pos -= getStringWidth(refreshTimeStr) + 25; + display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 21, wi_refresh_32x32, + 32, 32, dataColor); + pos -= sp; + + // status + dataColor = ACCENT_COLOR; + if (!statusStr.isEmpty()) + { + drawString(pos, DISP_HEIGHT - 1 - 2, statusStr, RIGHT, dataColor); + pos -= getStringWidth(statusStr) + 24; + display.drawInvertedBitmap(pos, DISP_HEIGHT - 1 - 18, error_icon_24x24, + 24, 24, dataColor); + } - return; -} // end drawStatusBar + return; + } // end drawStatusBar -/* This function is responsible for drawing prominent error messages to the - * screen. - * - * If error message line 2 (errMsgLn2) is empty, line 1 will be automatically - * wrapped. - */ -void drawError(const uint8_t *bitmap_196x196, - const String &errMsgLn1, const String &errMsgLn2) -{ - display.setFont(&FONT_26pt8b); - if (!errMsgLn2.isEmpty()) - { - drawString(DISP_WIDTH / 2, - DISP_HEIGHT / 2 + 196 / 2 + 21, - errMsgLn1, CENTER); - drawString(DISP_WIDTH / 2, - DISP_HEIGHT / 2 + 196 / 2 + 21 + 55, - errMsgLn2, CENTER); - } - else + /* This function is responsible for drawing prominent error messages to the + * screen. + * + * If error message line 2 (errMsgLn2) is empty, line 1 will be automatically + * wrapped. + */ + void drawError(const uint8_t *bitmap_196x196, + const String &errMsgLn1, const String &errMsgLn2) { - drawMultiLnString(DISP_WIDTH / 2, - DISP_HEIGHT / 2 + 196 / 2 + 21, - errMsgLn1, CENTER, DISP_WIDTH - 200, 2, 55); - } - display.drawInvertedBitmap(DISP_WIDTH / 2 - 196 / 2, - DISP_HEIGHT / 2 - 196 / 2 - 21, - bitmap_196x196, 196, 196, ACCENT_COLOR); - return; -} // end drawError - + display.setFont(&FONT_26pt8b); + if (!errMsgLn2.isEmpty()) + { + drawString(DISP_WIDTH / 2, + DISP_HEIGHT / 2 + 196 / 2 + 21, + errMsgLn1, CENTER); + drawString(DISP_WIDTH / 2, + DISP_HEIGHT / 2 + 196 / 2 + 21 + 55, + errMsgLn2, CENTER); + } + else + { + drawMultiLnString(DISP_WIDTH / 2, + DISP_HEIGHT / 2 + 196 / 2 + 21, + errMsgLn1, CENTER, DISP_WIDTH - 200, 2, 55); + } + display.drawInvertedBitmap(DISP_WIDTH / 2 - 196 / 2, + DISP_HEIGHT / 2 - 196 / 2 - 21, + bitmap_196x196, 196, 196, ACCENT_COLOR); + return; + } // end drawError