/* * Copyright Paul Warren 2019 * * Released under the terms of the GPLv3 license. */ #include #include #include #include #include #include #include "epd.h" #include "credential.h" // Weather bits // Australian BOM bits, Belconnen is mine. // see http://www.bom.gov.au/catalogue/data-feeds.shtml //const char* aac = "NSW_PT254"; //const char* bom_url = "ftp://ftp.bom.gov.au/anon/gen/fwo/IDN11060.xml"; // openweathermap, api_key from credential.h // const String owm_url = "http://api.openweathermap.org/data/2.5/weather?q=Belconnen,AU&units=metric&APPID="; // DarkSky see darksky.py //const String ds_url = "http://pwarren.id.au/darksky/darksy.json"; // BOM, see bom_xml.py const String ds_url = "http://pwarren.id.au/darksky/bom.json"; bool ds_done = false; // Timezone bits // Australia Eastern Time Zone (Sydney, Melbourne) TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours Timezone ausET(aEDT, aEST); TimeChangeRule *tcr; // Network clients WiFiUDP ntpUDP; HTTPClient http; NTPClient timeClient(ntpUDP, "raspberrypi.lan", 9, 60000); //use internal ntp server, update every 10 minutes, taking in to account the 9 seconds it takes to update the screen for the current time //NTPClient timeClient(ntpUDP, "au.pool.ntp.org", 0, 60000); //use internal ntp server, update every 10 minutes // defined in "credentials.h" const char* ssid = ssid_name; // The SSID (name) of your Wi-Fi network const char* password = ssid_pass; // The password of the Wi-Fi network const int led = LED_BUILTIN; // Font width int dwidth = 185; // yes this seems backwards but it works... void ledOn(void) { digitalWrite(led, LOW);} void ledOff(void) { digitalWrite(led, HIGH);} void wait(void) { delay(1500);} // globals for the temperature :( float current_outside; float current_bedroom; float current_lounge; void setup() { pinMode(led, OUTPUT); ledOff(); wifi_set_sleep_type(LIGHT_SLEEP_T); epd_init(); epd_wakeup(); epd_set_memory(MEM_NAND); epd_set_color(BLACK, WHITE); epd_set_en_font(ASCII64); epd_clear(); Serial.begin(115200); // Start serial communication Serial.setTimeout(2000); while (!Serial) { } Serial.println("\r\n\r\nSTARTING\r\n"); wifi(); timeClient.begin(); ntpUpdate(); epd_clear(); weatherUpdate(); printDateTime(1); epd_update(); ledOff(); } void wifi() { WiFi.persistent(false); WiFi.mode(WIFI_OFF); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Access WiFi Serial.print("Connecting to "); Serial.print(ssid); Serial.print(" ..."); while (WiFi.status() != WL_CONNECTED) { // Wait for WiFi to connect delay(250); Serial.print("."); } Serial.println('\n'); Serial.println("WiFi connection established"); Serial.print("Device's IP address is "); Serial.println(WiFi.localIP()); // Show device's IP address } void ntpUpdate() { if (timeClient.update()) { setTime(timeClient.getEpochTime()); } } void disp_digit(int digit, int xpos){ // digit: digit to display // xpos: X digit position, indexed at 1 int dpos = 0; int ypos = 0; dpos = ((xpos - 1) * dwidth); if (xpos > 2) { dpos += 60; // account for colon } switch (digit) { case 0: epd_disp_bitmap("0.JPG", dpos, ypos); break; case 1: epd_disp_bitmap("1.JPG", dpos, ypos); break; case 2: epd_disp_bitmap("2.JPG", dpos, ypos); break; case 3: epd_disp_bitmap("3.JPG", dpos, ypos); break; case 4: epd_disp_bitmap("4.JPG", dpos, ypos); break; case 5: epd_disp_bitmap("5.JPG", dpos, ypos); break; case 6: epd_disp_bitmap("6.JPG", dpos, ypos); break; case 7: epd_disp_bitmap("7.JPG", dpos, ypos); break; case 8: epd_disp_bitmap("8.JPG", dpos, ypos); break; case 9: epd_disp_bitmap("9.JPG", dpos, ypos); break; } wait(); } void disp_time(int hours, int minutes) { // first digit of hours if (hours > 19) { disp_digit(2, 1); hours -= 20; } else if (hours > 9) { disp_digit(1, 1); hours -= 10; } else { disp_digit(0, 1); } // second hours digit disp_digit(hours, 2); epd_disp_bitmap("COLO.JPG", 2 * dwidth, 0); delay(500); // first minutes digit if (minutes > 49) { disp_digit(5, 3); minutes -= 50; } else if (minutes > 39) { disp_digit(4, 3); minutes -= 40; } else if (minutes > 29) { disp_digit(3, 3); minutes -= 30; } else if (minutes > 19) { disp_digit(2, 3); minutes -= 20; } else if (minutes > 9) { disp_digit(1, 3); minutes -= 10; } else { disp_digit(0, 3); } // second minutes digit disp_digit(minutes, 4); } void printDateTime(int update_epd) { char buf[64]; time_t t; t = ausET.toLocal(now(), &tcr); char m[4]; // temporary storage for month string (DateStrings.cpp uses shared buffer) strcpy(m, monthShortStr(month(t))); sprintf(buf, "%.2d:%.2d:%.2d", hour(t), minute(t), second(t)); Serial.print(buf); Serial.print(" "); if (update_epd > 0) { disp_time(hour(t), minute(t)); wait(); } sprintf(buf, "%s %.2d %s %d Ext: %.2f Int: %.2f", dayShortStr(weekday(t)), day(t), m, year(t), current_outside, current_lounge); Serial.println(buf); if (update_epd > 0) { epd_set_en_font(ASCII64); epd_disp_string(buf, 5, 225); wait(); } } void weatherUpdate(void) { const size_t capacity = 4*JSON_OBJECT_SIZE(3) + 150; DynamicJsonBuffer jsonBuffer(capacity); http.begin(ds_url); int httpCode = http.GET(); if (httpCode > 0) { JsonObject& root = jsonBuffer.parseObject(http.getString()); if (!root.success()) { Serial.print(F("parseObject() failed: ")); return; } http.end(); JsonObject& today = root["today"]; const char* today_day = today["day"]; // "Wednesday" const char* today_icon = today["icon"]; // "sunny.jpg" const char* today_max = today["max"]; // "27" JsonObject& tomorrow = root["tomorrow"]; const char* tomorrow_day = tomorrow["day"]; // "Thursday" const char* tomorrow_icon = tomorrow["icon"]; // "sunny.jpg" const char* tomorrow_max = tomorrow["max"]; // "31" JsonObject& day_after = root["day_after"]; const char* day_after_day = day_after["day"]; // "Friday" const char* day_after_icon = day_after["icon"]; // "pcloud.jpg" const char* day_after_max = day_after["max"]; // "33" JsonObject current = doc["current"]; current_outside = current["outside"]; current_bedroom = current["bedroom"]; current_lounge = current["lounge"]; Serial.println("Updating EPD"); epd_set_en_font(ASCII64); //Today epd_disp_bitmap(today_icon, 0, 290); wait(); epd_disp_string(today_day, 0, 536); wait(); epd_disp_string(today_max, 75, 460); wait(); //Tomorrow epd_disp_bitmap(tomorrow_icon, 267, 290); wait(); epd_disp_string(tomorrow_day, 260, 536); wait(); epd_disp_string(tomorrow_max, 352, 460); wait(); //Day After epd_disp_bitmap(day_after_icon, 533, 290); wait(); epd_disp_string(day_after_day, 533, 536); wait(); epd_disp_string(day_after_max, 615, 460); wait(); } else { Serial.print("Error on HTTP Request: "); Serial.println(httpCode); } } void loop() { int wakeup_seconds = 43; int sleep_seconds = 0; Serial.println("Back from sleep!"); if (WiFi.status() != WL_CONNECTED) { wifi(); } ntpUpdate(); if (second(now()) >= wakeup_seconds) { epd_wakeup(); epd_clear(); weatherUpdate(); Serial.print("Weather details sent: "); Serial.println(second(now())); while (second(now()) != 0) { delay(500); } printDateTime(1); epd_update(); delay(3000); epd_enter_stopmode(); } sleep_seconds = wakeup_seconds - second(now()); Serial.print("Sleeping for: "); Serial.println(sleep_seconds); delay(sleep_seconds * 1000); }