Add fetching from wttr.in and moon display
All checks were successful
Compile / Compile (push) Successful in 1m26s
All checks were successful
Compile / Compile (push) Successful in 1m26s
This commit is contained in:
parent
e11bcdc9cd
commit
1b6f89faea
5 changed files with 344 additions and 43 deletions
39
src/Icons.h
39
src/Icons.h
|
@ -23,6 +23,7 @@ namespace Icons
|
||||||
|
|
||||||
namespace Weather
|
namespace Weather
|
||||||
{
|
{
|
||||||
|
// 30x30
|
||||||
const unsigned char wind[] PROGMEM = {
|
const unsigned char wind[] PROGMEM = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
@ -34,6 +35,7 @@ namespace Icons
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 30x30
|
||||||
const unsigned char humidity[] PROGMEM = {
|
const unsigned char humidity[] PROGMEM = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00,
|
||||||
|
@ -45,6 +47,41 @@ namespace Icons
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 30x30
|
||||||
|
const unsigned char sunrise [] PROGMEM = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
|
||||||
|
0x01, 0x80, 0x06, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x18, 0x60, 0x00,
|
||||||
|
0x00, 0x30, 0x30, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x60, 0x18, 0x00, 0x1e, 0x60, 0x19, 0xe0,
|
||||||
|
0x00, 0x60, 0x18, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00,
|
||||||
|
0x00, 0x7c, 0xfc, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
// 30x30
|
||||||
|
const unsigned char sunset [] PROGMEM = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00,
|
||||||
|
0x01, 0x80, 0x06, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x1c, 0xe0, 0x00,
|
||||||
|
0x00, 0x30, 0x30, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x60, 0x18, 0x00, 0x1e, 0x60, 0x19, 0xe0,
|
||||||
|
0x00, 0x60, 0x18, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x78, 0x78, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char noon [] PROGMEM = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x73, 0x38, 0x00, 0x00, 0xc3, 0x0c, 0x00,
|
||||||
|
0x01, 0x83, 0x06, 0x00, 0x03, 0x03, 0x03, 0x00, 0x02, 0x03, 0x01, 0x00, 0x06, 0x03, 0x01, 0x80,
|
||||||
|
0x06, 0x03, 0x00, 0x80, 0x04, 0x03, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80,
|
||||||
|
0x04, 0x00, 0x00, 0x80, 0x06, 0x00, 0x01, 0x80, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00,
|
||||||
|
0x01, 0x80, 0x06, 0x00, 0x00, 0xc0, 0x0c, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x3f, 0xf0, 0x00,
|
||||||
|
0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
// 50x50
|
// 50x50
|
||||||
const unsigned char day_clear[] PROGMEM = {
|
const unsigned char day_clear[] PROGMEM = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
@ -96,7 +133,7 @@ namespace Icons
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
|
|
||||||
const unsigned char night_clear [] PROGMEM = {
|
const unsigned char night_clear [] PROGMEM = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
|
@ -6,13 +6,30 @@
|
||||||
#include <Fonts/FreeSans12pt7b.h>
|
#include <Fonts/FreeSans12pt7b.h>
|
||||||
#include <Fonts/FreeSans18pt7b.h>
|
#include <Fonts/FreeSans18pt7b.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
RTC_DATA_ATTR uint64_t WatchFacePages::Weather::m_lastSyncTime = 0;
|
RTC_DATA_ATTR uint64_t WatchFacePages::Weather::m_lastSyncTime = 0;
|
||||||
RTC_DATA_ATTR int WatchFacePages::Weather::m_lastCalculatedDay = 0XFFFFFFFF;
|
RTC_DATA_ATTR int WatchFacePages::Weather::m_lastCalculatedDay = 0XFFFFFFFF;
|
||||||
RTC_DATA_ATTR float WatchFacePages::Weather::m_locationLat = DEFAULT_LATITUDE;
|
RTC_DATA_ATTR double WatchFacePages::Weather::m_locationLat = DEFAULT_LATITUDE;
|
||||||
RTC_DATA_ATTR float WatchFacePages::Weather::m_locationLon = DEFAULT_LONGITUDE;
|
RTC_DATA_ATTR double WatchFacePages::Weather::m_locationLon = DEFAULT_LONGITUDE;
|
||||||
RTC_DATA_ATTR char WatchFacePages::Weather::m_locationCity[128];
|
RTC_DATA_ATTR char WatchFacePages::Weather::m_locationCity[128];
|
||||||
|
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_sunriseHour;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_sunriseMinute;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_noonHour;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_noonMinute;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_sunsetHour;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_sunsetMinute;
|
||||||
|
RTC_DATA_ATTR double WatchFacePages::Weather::m_moonPhase;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_moonBitmap[8 * 64];
|
||||||
|
|
||||||
|
RTC_DATA_ATTR double WatchFacePages::Weather::m_currentTemperature;
|
||||||
|
RTC_DATA_ATTR float WatchFacePages::Weather::m_currentWindSpeed;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_currentHumidity;
|
||||||
|
RTC_DATA_ATTR uint8_t WatchFacePages::Weather::m_currentWeatherIcon;
|
||||||
|
|
||||||
WatchFacePages::Weather::Weather(WatchyDisplay & display, WatchFeatures::WatchFeatures & features)
|
WatchFacePages::Weather::Weather(WatchyDisplay & display, WatchFeatures::WatchFeatures & features)
|
||||||
: m_display(display), m_features(features)
|
: m_display(display), m_features(features)
|
||||||
{
|
{
|
||||||
|
@ -37,20 +54,10 @@ void WatchFacePages::Weather::DrawPage(bool partialRefresh)
|
||||||
m_display.fillScreen(GxEPD_WHITE);
|
m_display.fillScreen(GxEPD_WHITE);
|
||||||
m_display.setTextColor(GxEPD_BLACK);
|
m_display.setTextColor(GxEPD_BLACK);
|
||||||
|
|
||||||
if (m_lastSyncTime == 0) {
|
|
||||||
m_display.setFont(&FreeSans12pt7b);
|
|
||||||
int16_t x, y;
|
|
||||||
uint16_t w, h;
|
|
||||||
m_display.getTextBounds("Have not synced", 0, 0, &x, &y, &w, &h);
|
|
||||||
m_display.setCursor(DISPLAY_WIDTH / 2 - w / 2, 110);
|
|
||||||
m_display.print("Have not synced");
|
|
||||||
m_display.display(partialRefresh);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool weatherOutdated = false;
|
bool weatherOutdated = false;
|
||||||
|
|
||||||
if (m_features.rtc.GetTimestamp() - m_lastSyncTime > 86400) {
|
// If it hasn't synced in 24 hours, assume weather is outdated
|
||||||
|
if (m_lastSyncTime == 0 || m_features.rtc.GetTimestamp() - m_lastSyncTime > 86400) {
|
||||||
weatherOutdated = true;
|
weatherOutdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,42 +67,78 @@ void WatchFacePages::Weather::DrawPage(bool partialRefresh)
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
|
|
||||||
m_display.drawBitmap(5, 5, Icons::city, 29, 23, GxEPD_BLACK);
|
m_display.drawBitmap(5, 5, Icons::city, 29, 23, GxEPD_BLACK);
|
||||||
|
|
||||||
m_display.setCursor(39, 22);
|
m_display.setCursor(39, 22);
|
||||||
m_display.print(m_locationCity);
|
m_display.print(m_locationCity);
|
||||||
// Separator
|
// Separator
|
||||||
m_display.fillRect(10, 35, DISPLAY_WIDTH - 20, 2, GxEPD_BLACK);
|
m_display.fillRect(10, 35, DISPLAY_WIDTH - 20, 2, GxEPD_BLACK);
|
||||||
|
|
||||||
|
// Moon
|
||||||
|
m_display.drawBitmap(200 - 64 - 10, 45, m_moonBitmap, 64, 64, GxEPD_BLACK);
|
||||||
|
m_display.drawCircle(200 - 64 - 10 + 32, 45 + 32, 32, GxEPD_BLACK);
|
||||||
|
|
||||||
|
// Sunrise, solar noon and sunset
|
||||||
|
m_display.drawBitmap(10, 38, Icons::Weather::sunrise, 30, 30, GxEPD_BLACK);
|
||||||
|
std::ostringstream sunrise;
|
||||||
|
sunrise << std::setfill('0') << std::setw(2) << (int)m_sunriseHour << ":" << std::setw(2) << (int)m_sunriseMinute;
|
||||||
|
m_display.setFont(&FreeSans9pt7b);
|
||||||
|
m_display.setCursor(45, 56);
|
||||||
|
m_display.setTextColor(GxEPD_BLACK);
|
||||||
|
m_display.print(sunrise.str().c_str());
|
||||||
|
|
||||||
|
m_display.drawBitmap(10, 62, Icons::Weather::noon, 30, 30, GxEPD_BLACK);
|
||||||
|
std::ostringstream noon;
|
||||||
|
noon << std::setfill('0') << std::setw(2) << (int)m_noonHour << ":" << std::setw(2) << (int)m_noonMinute;
|
||||||
|
m_display.setCursor(45, 80);
|
||||||
|
m_display.print(noon.str().c_str());
|
||||||
|
|
||||||
|
m_display.drawBitmap(10, 87, Icons::Weather::sunset, 30, 30, GxEPD_BLACK);
|
||||||
|
std::ostringstream sunset;
|
||||||
|
sunset << std::setfill('0') << std::setw(2) << (int)m_sunsetHour << ":" << std::setw(2) << (int)m_sunsetMinute;
|
||||||
|
m_display.setFont(&FreeSans9pt7b);
|
||||||
|
m_display.setCursor(45, 106);
|
||||||
|
m_display.print(sunset.str().c_str());
|
||||||
|
|
||||||
// Temperature and weather icon
|
// Temperature and weather icon
|
||||||
// Separator
|
// Separator
|
||||||
m_display.fillRect(10, 116, DISPLAY_WIDTH - 20, 2, GxEPD_BLACK);
|
m_display.fillRect(10, 116, DISPLAY_WIDTH - 20, 2, GxEPD_BLACK);
|
||||||
|
|
||||||
m_display.drawBitmap(5, 125, Icons::Weather::day_clear, 75, 75, GxEPD_BLACK);
|
if (weatherOutdated) {
|
||||||
m_display.setFont(&FreeSans12pt7b);
|
m_display.setFont(&FreeSans9pt7b);
|
||||||
|
m_display.getTextBounds("No Weather Data", 0, 0, &x, &y, &w, &h);
|
||||||
|
m_display.setCursor(DISPLAY_WIDTH / 2 - w / 2, 162);
|
||||||
|
m_display.print("No Weather Data");
|
||||||
|
} else {
|
||||||
|
m_display.drawBitmap(5, 125, Icons::Weather::day_clear, 75, 75, GxEPD_BLACK);
|
||||||
|
m_display.setFont(&FreeSans12pt7b);
|
||||||
|
|
||||||
std::string temperature = "-20.5 C";
|
std::ostringstream temperature;
|
||||||
|
temperature << (int)m_currentTemperature << " C";
|
||||||
|
|
||||||
m_display.getTextBounds(temperature.c_str(), 0, 0, &x, &y, &w, &h);
|
m_display.getTextBounds(temperature.str().c_str(), 0, 0, &x, &y, &w, &h);
|
||||||
m_display.setCursor(190 - w, 142);
|
m_display.setCursor(190 - w, 142);
|
||||||
m_display.print(temperature.c_str());
|
m_display.print(temperature.str().c_str());
|
||||||
// Hacky degree symbol
|
// Hacky degree symbol
|
||||||
m_display.fillCircle(190 - 23, 129, 3, GxEPD_BLACK);
|
m_display.fillCircle(190 - 23, 129, 3, GxEPD_BLACK);
|
||||||
m_display.fillCircle(190 - 23, 129, 1, GxEPD_WHITE);
|
m_display.fillCircle(190 - 23, 129, 1, GxEPD_WHITE);
|
||||||
|
|
||||||
// Wind speed
|
// Wind speed
|
||||||
m_display.drawBitmap(165, 145, Icons::Weather::wind, 30, 30, GxEPD_BLACK);
|
m_display.drawBitmap(165, 145, Icons::Weather::wind, 30, 30, GxEPD_BLACK);
|
||||||
std::string windSpeed = "99 m/s";
|
std::ostringstream windSpeed;
|
||||||
m_display.setFont(&FreeSans9pt7b);
|
windSpeed << std::fixed << std::setprecision(1) << m_currentWindSpeed << " m/s";
|
||||||
m_display.getTextBounds(windSpeed.c_str(), 0, 0, &x, &y, &w, &h);
|
|
||||||
m_display.setCursor(160 - w, 165);
|
|
||||||
m_display.print(windSpeed.c_str());
|
|
||||||
|
|
||||||
// Humidity
|
m_display.setFont(&FreeSans9pt7b);
|
||||||
m_display.drawBitmap(165, 170, Icons::Weather::humidity, 30, 30, GxEPD_BLACK);
|
m_display.getTextBounds(windSpeed.str().c_str(), 0, 0, &x, &y, &w, &h);
|
||||||
std::string humidity = "100";
|
m_display.setCursor(160 - w, 165);
|
||||||
m_display.getTextBounds(humidity.c_str(), 0, 0, &x, &y, &w, &h);
|
m_display.print(windSpeed.str().c_str());
|
||||||
m_display.setCursor(160 - w, 190);
|
|
||||||
m_display.print(humidity.c_str());
|
// Humidity
|
||||||
|
m_display.drawBitmap(165, 170, Icons::Weather::humidity, 30, 30, GxEPD_BLACK);
|
||||||
|
std::ostringstream humidity;
|
||||||
|
humidity << (int)m_currentHumidity;
|
||||||
|
m_display.getTextBounds(humidity.str().c_str(), 0, 0, &x, &y, &w, &h);
|
||||||
|
m_display.setCursor(160 - w, 190);
|
||||||
|
m_display.print(humidity.str().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
m_display.display(partialRefresh);
|
m_display.display(partialRefresh);
|
||||||
}
|
}
|
||||||
|
@ -112,10 +155,11 @@ void WatchFacePages::Weather::Resync()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DO_GEOLOCATION) {
|
HTTPClient client;
|
||||||
HTTPClient client;
|
client.setConnectTimeout(3000); // 3 second max timeout
|
||||||
client.setConnectTimeout(3000); // 3 second max timeout
|
|
||||||
|
|
||||||
|
// Get geolocation from IP
|
||||||
|
if (DO_GEOLOCATION) {
|
||||||
client.begin("http://ip-api.com/line/");
|
client.begin("http://ip-api.com/line/");
|
||||||
int httpCode = client.GET();
|
int httpCode = client.GET();
|
||||||
if (httpCode != 200) {
|
if (httpCode != 200) {
|
||||||
|
@ -151,7 +195,167 @@ void WatchFacePages::Weather::Resync()
|
||||||
m_locationLon = std::stof(lines[8]);
|
m_locationLon = std::stof(lines[8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://wttr.in/60.170833,24.9375?0Q&format=+%x:%t:%h
|
||||||
|
std::ostringstream url;
|
||||||
|
url << "http://wttr.in/" << m_locationLat << "," << m_locationLon << "?0Q&format=%x:%t:%h:%w";
|
||||||
|
// Grr. wttr.in does a redirect to HTTPS if your agent isn't curl
|
||||||
|
client.setUserAgent("curl/8.0.1");
|
||||||
|
|
||||||
|
client.begin(url.str().c_str());
|
||||||
|
int httpCode = client.GET();
|
||||||
m_features.wifi.Disconnect();
|
m_features.wifi.Disconnect();
|
||||||
|
if (httpCode != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String payload = client.getString();
|
||||||
|
client.end();
|
||||||
|
std::string reponse(payload.c_str());
|
||||||
|
// Split on :
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
std::string::size_type pos = 0;
|
||||||
|
std::string::size_type prev = 0;
|
||||||
|
char delimiter = ':';
|
||||||
|
while ((pos = reponse.find(delimiter, prev)) != std::string::npos) {
|
||||||
|
parts.push_back(reponse.substr(prev, pos - prev));
|
||||||
|
prev = pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push_back(reponse.substr(prev));
|
||||||
|
|
||||||
|
if (parts.size() != 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown, Cloudy, Fog, Heavy Rain, Heavy Showers, Heavy Snow, Heavy Snow Showers, Light Rain, Light Showers, Light Sleet, Light Sleet Showers, Light Snow, Light Snow Showers, Partly Cloudy, Sunny, Thundery Heavy Rain, Thundery Showers, Thundery Snow Showers, Very Cloudy
|
||||||
|
std::vector<std::string> iconLookupTable = {"?", "mm", "=", "///", "//", "**", "/*/", "/", ".", "x", "x/", "*", "*/", "m", "o", "/!/", "!/", "*!*", "mmm"};
|
||||||
|
std::string iconName = parts[1];
|
||||||
|
for (int i = 0; i < iconLookupTable.size(); i++) {
|
||||||
|
if (iconName == iconLookupTable[i]) {
|
||||||
|
m_currentWeatherIcon = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string temperature = parts[2].substr(0, parts[2].length() - 2);
|
||||||
|
m_currentTemperature = std::stoi(temperature);
|
||||||
|
|
||||||
|
std::string humidity = parts[2].substr(0, parts[2].length() - 1);
|
||||||
|
m_currentHumidity = std::stoi(humidity);
|
||||||
|
|
||||||
|
std::string windSpeed = parts[3].substr(3, parts[3].length() - 7);
|
||||||
|
float windSpeedKmh = std::stof(windSpeed);
|
||||||
|
|
||||||
|
// Convert to m/s
|
||||||
|
m_currentWindSpeed = std::round(windSpeedKmh * 0.277778);
|
||||||
|
|
||||||
|
m_features.wifi.Disconnect();
|
||||||
|
Recalc();
|
||||||
|
|
||||||
m_lastSyncTime = currentTime;
|
m_lastSyncTime = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WatchFacePages::Weather::Recalc()
|
||||||
|
{
|
||||||
|
tmElements_t tm;
|
||||||
|
m_features.rtc.Get(tm);
|
||||||
|
|
||||||
|
if (m_lastCalculatedDay == tm.Day) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int dayOfYear = m_features.rtc.GetDayOfYear(tm);
|
||||||
|
double denominator = tm.Year / 4 ? 366.0 : 365.0;
|
||||||
|
double fractionalYear = (2.0 * M_PI / denominator) * (dayOfYear - 1 + ((0 - 12.0) / 24.0));
|
||||||
|
|
||||||
|
double eqtime = 229.18 * (0.000075 + 0.001868 * cos(fractionalYear) - 0.032077 * sin(fractionalYear) - 0.014615 * cos(2.0 * fractionalYear) - 0.040849 * sin(2.0 * fractionalYear));
|
||||||
|
double declination = 0.006918 - 0.399912 * cos(fractionalYear) + 0.070257 * sin(fractionalYear) - 0.006758 * cos(2.0 * fractionalYear) + 0.000907 * sin(2.0 * fractionalYear) - 0.002697 * cos(3.0 * fractionalYear) + 0.00148 * sin(3.0 * fractionalYear);
|
||||||
|
|
||||||
|
const double latitudeRadians = (double)m_locationLat * M_PI / 180.0;
|
||||||
|
const double longitudeRadians = (double)m_locationLon * M_PI / 180.0;
|
||||||
|
const double sunriseSunsetAngle = 1.5853349200000815;
|
||||||
|
|
||||||
|
double timeOffset = eqtime + 4.0 * (double)m_locationLon - 60.0;
|
||||||
|
double trueSolarTime = tm.Hour * 60.0 + tm.Minute + tm.Second / 60.0 + timeOffset;
|
||||||
|
double hourAngle = acos(cos(sunriseSunsetAngle) / (cos(latitudeRadians) * cos(declination)) - tan(latitudeRadians) * tan(declination));
|
||||||
|
hourAngle = hourAngle * 180.0 / M_PI;
|
||||||
|
double sunrise = 720 - 4.0 * ((double)m_locationLon + hourAngle) - eqtime;
|
||||||
|
double sunset = 720 - 4.0 * ((double)m_locationLon - hourAngle) - eqtime;
|
||||||
|
double noon = 720 - 4.0 * ((double)m_locationLon) - eqtime;
|
||||||
|
|
||||||
|
sunrise += m_features.storage.GetTzOffset() / 60;
|
||||||
|
sunset += m_features.storage.GetTzOffset() / 60;
|
||||||
|
noon += m_features.storage.GetTzOffset() / 60;
|
||||||
|
|
||||||
|
m_sunriseHour = sunrise / 60;
|
||||||
|
m_sunriseMinute = sunrise - m_sunriseHour * 60;
|
||||||
|
m_sunsetHour = sunset / 60;
|
||||||
|
m_sunsetMinute = sunset - m_sunsetHour * 60;
|
||||||
|
m_noonHour = noon / 60;
|
||||||
|
m_noonMinute = noon - m_noonHour * 60;
|
||||||
|
|
||||||
|
tmElements_t newMoon;
|
||||||
|
newMoon.Year = 2023 - 1970;
|
||||||
|
newMoon.Month = 5;
|
||||||
|
newMoon.Day = 19;
|
||||||
|
|
||||||
|
unsigned int daysDifference = m_features.rtc.DaysDifference(newMoon, tm);
|
||||||
|
m_moonPhase = fmod((double)daysDifference, 29.530588853) / 29.530588853;
|
||||||
|
|
||||||
|
// Draw a 1-bit bitmap with the moon phase
|
||||||
|
bool pixels[64 * 64];
|
||||||
|
for (int i = 0; i < 64 * 64; i++) {
|
||||||
|
pixels[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < 64; x++) {
|
||||||
|
for (int y = 0; y < 64; y++) {
|
||||||
|
float distance = sqrt(powf(fabsf(x - 32), 2) + powf(fabsf(y - 32), 2));
|
||||||
|
if (distance < 32) {
|
||||||
|
float inclination = asinf(((float)y - 32.0f) / 32.0f);
|
||||||
|
float azimuth = acosf((float)(x - 32) / (-32.0f * cosf(inclination)));
|
||||||
|
|
||||||
|
azimuth += M_PI;
|
||||||
|
|
||||||
|
azimuth += (m_moonPhase) * M_PI * 2.0f;
|
||||||
|
while (azimuth > M_PI * 2.0f) {
|
||||||
|
azimuth -= M_PI * 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (azimuth < 0) {
|
||||||
|
azimuth += M_PI * 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (azimuth > M_PI) {
|
||||||
|
pixels[x + y * 64] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pixels[x + y * 64] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs to be split into two parts because pio does too aggressive optimisation
|
||||||
|
for (unsigned int y = 0; y < 32; y++) {
|
||||||
|
for (unsigned int x = 0; x < 8; x++) {
|
||||||
|
uint8_t value = 0;
|
||||||
|
for (unsigned int i = 0; i < 8; i++) {
|
||||||
|
value |= pixels[(x * 8 + i) + (y * 64)] << (7 - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_moonBitmap[x + y * 8] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int y = 32; y < 64; y++) {
|
||||||
|
for (unsigned int x = 0; x < 8; x++) {
|
||||||
|
uint8_t value = 0;
|
||||||
|
for (unsigned int i = 0; i < 8; i++) {
|
||||||
|
value |= pixels[(x * 8 + i) + (y * 64)] << (7 - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_moonBitmap[x + y * 8] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastCalculatedDay = tm.Day;
|
||||||
|
}
|
|
@ -22,11 +22,26 @@ public:
|
||||||
void Resync();
|
void Resync();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void Recalc(); // Offline daily ephemeris like sunrise/sunset
|
||||||
|
|
||||||
WatchyDisplay & m_display;
|
WatchyDisplay & m_display;
|
||||||
WatchFeatures::WatchFeatures & m_features;
|
WatchFeatures::WatchFeatures & m_features;
|
||||||
static RTC_DATA_ATTR uint64_t m_lastSyncTime;
|
static RTC_DATA_ATTR uint64_t m_lastSyncTime;
|
||||||
static RTC_DATA_ATTR int m_lastCalculatedDay;
|
static RTC_DATA_ATTR int m_lastCalculatedDay;
|
||||||
static RTC_DATA_ATTR char m_locationCity[128];
|
static RTC_DATA_ATTR char m_locationCity[128];
|
||||||
static RTC_DATA_ATTR float m_locationLat, m_locationLon;
|
static RTC_DATA_ATTR double m_locationLat, m_locationLon;
|
||||||
|
|
||||||
|
static RTC_DATA_ATTR uint8_t m_sunriseHour;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_sunriseMinute;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_noonHour;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_noonMinute;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_sunsetHour;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_sunsetMinute;
|
||||||
|
static RTC_DATA_ATTR double m_moonPhase;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_moonBitmap[8 * 64];
|
||||||
|
|
||||||
|
static RTC_DATA_ATTR double m_currentTemperature;
|
||||||
|
static RTC_DATA_ATTR float m_currentWindSpeed;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_currentHumidity;
|
||||||
|
static RTC_DATA_ATTR uint8_t m_currentWeatherIcon;
|
||||||
};
|
};
|
|
@ -106,6 +106,49 @@ void WatchFeatures::RTC::OffsetTime(tmElements_t & tm, int offsetInSeconds)
|
||||||
breakTime(t, tm);
|
breakTime(t, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int WatchFeatures::RTC::GetDayOfYear(tmElements_t & tm)
|
||||||
|
{
|
||||||
|
unsigned int dayOfYear = 1;
|
||||||
|
for (int i = 1; i < tm.Month; i++) {
|
||||||
|
switch (i) {
|
||||||
|
case 2:
|
||||||
|
dayOfYear += (tm.Year % 4 == 0) ? 29 : 28;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
case 9:
|
||||||
|
case 11:
|
||||||
|
dayOfYear += 30;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dayOfYear += 31;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dayOfYear += tm.Day;
|
||||||
|
return dayOfYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int WatchFeatures::RTC::DaysDifference(tmElements_t & tm1, tmElements_t & tm2)
|
||||||
|
{
|
||||||
|
unsigned int dayOfYear1 = GetDayOfYear(tm1);
|
||||||
|
unsigned int dayOfYear2 = GetDayOfYear(tm2);
|
||||||
|
unsigned int days = 0;
|
||||||
|
|
||||||
|
if (tm1.Year == tm2.Year) {
|
||||||
|
days = dayOfYear2 - dayOfYear1;
|
||||||
|
} else {
|
||||||
|
days = 365 - dayOfYear1 + dayOfYear2;
|
||||||
|
for (int i = tm1.Year + 1; i < tm2.Year; i++) {
|
||||||
|
days += (i % 4 == 0) ? 366 : 365;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: implement more advanced wakeup logic, i.e. > 255 seconds that are not a multiple of 60
|
// TODO: implement more advanced wakeup logic, i.e. > 255 seconds that are not a multiple of 60
|
||||||
bool WatchFeatures::RTC::CheckWakeup()
|
bool WatchFeatures::RTC::CheckWakeup()
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,8 @@ public:
|
||||||
void Resync(); // Resync the timer cycle, both initially and after RTC resync
|
void Resync(); // Resync the timer cycle, both initially and after RTC resync
|
||||||
|
|
||||||
static void OffsetTime(tmElements_t & tm, int offsetInSeconds);
|
static void OffsetTime(tmElements_t & tm, int offsetInSeconds);
|
||||||
|
static unsigned int GetDayOfYear(tmElements_t & tm);
|
||||||
|
static unsigned int DaysDifference(tmElements_t & tm1, tmElements_t & tm2);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Rtc_Pcf8563 m_rtcPcf;
|
Rtc_Pcf8563 m_rtcPcf;
|
||||||
|
|
Loading…
Reference in a new issue