diff --git a/marquee/OpenWeatherMapClient.cpp b/marquee/OpenWeatherMapClient.cpp index dd4c07d..003b740 100644 --- a/marquee/OpenWeatherMapClient.cpp +++ b/marquee/OpenWeatherMapClient.cpp @@ -31,7 +31,7 @@ OpenWeatherMapClient::OpenWeatherMapClient(String ApiKey, int CityIDs[], int cit void OpenWeatherMapClient::updateWeather() { WiFiClient weatherClient; - String apiGetData = "GET /data/2.5/group?id=" + myCityIDs + "&units=" + units + "&cnt=1&APPID=" + myApiKey; + String apiGetData = "GET /data/2.5/group?id=" + myCityIDs + "&units=" + units + "&cnt=1&APPID=" + myApiKey + " HTTP/1.1"; Serial.println("Getting Weather Data"); Serial.println(apiGetData); @@ -53,6 +53,23 @@ void OpenWeatherMapClient::updateWeather() { Serial.println("Waiting for data"); + // Check HTTP status + char status[32] = {0}; + weatherClient.readBytesUntil('\r', status, sizeof(status)); + Serial.println("Response Header: " + String(status)); + if (strcmp(status, "HTTP/1.1 200 OK") != 0) { + Serial.print(F("Unexpected response: ")); + Serial.println(status); + return; + } + + // Skip HTTP headers + char endOfHeaders[] = "\r\n\r\n"; + if (!weatherClient.find(endOfHeaders)) { + Serial.println(F("Invalid response")); + return; + } + while (weatherClient.connected() || weatherClient.available()) { //connected or data available char c = weatherClient.read(); //gets byte from ethernet buffer result = result+c; diff --git a/marquee/marquee.ino b/marquee/marquee.ino index 0356864..c708c89 100644 --- a/marquee/marquee.ino +++ b/marquee/marquee.ino @@ -1,1056 +1,1056 @@ -/** The MIT License (MIT) - -Copyright (c) 2018 David Payne - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - - /********************************************** - * Edit Settings.h for personalization - ***********************************************/ - -#include "Settings.h" - -#define VERSION "1.5" - -#define HOSTNAME "ESP8266-" -#define CONFIG "/conf.txt" -#define BUZZER_PIN D2 - -/* Useful Constants */ -#define SECS_PER_MIN (60UL) -#define SECS_PER_HOUR (3600UL) -#define SECS_PER_DAY (SECS_PER_HOUR * 24L) - -/* Useful Macros for getting elapsed time */ -#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) -#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) -#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) -#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) - -//declairing prototypes -void configModeCallback (WiFiManager *myWiFiManager); -int8_t getWifiQuality(); - -// LED Settings -const int offset = 1; -const int numberOfHorizontalDisplays = 4; -const int numberOfVerticalDisplays = 1; -int refresh = 0; -String message = "hello"; -int wait = 25; // In milliseconds -- controls speed of scroll -int spacer = 1; // dots between letters -int width = 5 + spacer; // The font width is 5 pixels + spacer -Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays); - -float UtcOffset; //time zone offsets that correspond with the CityID above (offset from GMT) - -// Time -TimeClient timeClient(UtcOffset); -String lastMinute = "xx"; -long lastEpoch = 0; -long firstEpoch = 0; -long displayOffEpoch = 0; -boolean displayOn = true; -boolean timeOffsetFetched = false; - -// News Client -NewsApiClient newsClient(NEWS_API_KEY, NEWS_SOURCE); -int newsIndex = 0; - -// Advice Client -AdviceSlipClient adviceClient; - -// Weather Client -OpenWeatherMapClient weatherClient(APIKEY, CityIDs, 1, IS_METRIC); - -// OctoPrint Client -OctoPrintClient printerClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); -int printerCount = 0; - -ESP8266WebServer server(WEBSERVER_PORT); - -const String WEB_ACTIONS = " Home" - " Configure" - " Refresh Data" - "%TOGGLEDISPLAY%" - " Reset Settings" - " Forget WiFi" - " About"; - -const String CHANGE_FORM1 = "

City ID:

" - "" - "

Search for City ID

" - " Use 24 Hour Clock (military time)

" - " Use Metric (Celsius)

" - " Display News Headlines

" - "Select News Source

"; - -const String CHANGE_FORM2 = " Display Advice

" - "" - "

" - "

" - "Select Display Brightness

" - "Refresh Data (minutes)

" - " Show OctoPrint Status

" - "" - "" - "" - "


" - " Use Security Credentials for Configuration Changes

" - "" - "" - "

" - ""; - -const String NEWS_OPTIONS = "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - ""; - -const int TIMEOUT = 500; // 500 = 1/2 second -int timeoutCount = 0; - -// Change the externalLight to the pin you wish to use if other than the Built-in LED -int externalLight = LED_BUILTIN; // LED_BUILTIN is is the built in LED on the Wemos - -void setup() { - Serial.begin(115200); - SPIFFS.begin(); - //SPIFFS.remove(CONFIG); - delay(10); - - // Initialize digital pin for LED - pinMode(externalLight, OUTPUT); - - //New Line to clear from start garbage - Serial.println(); - - readCityIds(); - - // initialize dispaly - matrix.setIntensity(0); // Use a value between 0 and 15 for brightness - matrix.setRotation(0,3); - matrix.setRotation(1,3); - matrix.setRotation(2,3); - matrix.setRotation(3,3); - - -// Adjust to your own needs - matrix.setPosition(0, 3, 0); // The first display is at <0, 7> - matrix.setPosition(1, 2, 0); // The second display is at <1, 0> - matrix.setPosition(2, 1, 0); // The third display is at <2, 0> - matrix.setPosition(3, 0, 0); // And the last display is at <3, 0> - Serial.println("matrix created"); - matrix.fillScreen(LOW); // show black - centerPrint("hello"); - - tone(BUZZER_PIN, 415, 500); - delay(500*1.3); - tone(BUZZER_PIN, 466, 500); - delay(500*1.3); - tone(BUZZER_PIN, 370, 1000); - delay(1000*1.3); - noTone(BUZZER_PIN); - - for (int inx = 0; inx <= 15; inx++) { - matrix.setIntensity(inx); - delay(100); - } - for (int inx = 15; inx > 0; inx--) { - matrix.setIntensity(inx); - delay(60); - } - delay(1000); - matrix.setIntensity(displayIntensity); - //noTone(BUZZER_PIN); - - //WiFiManager - //Local intialization. Once its business is done, there is no need to keep it around - WiFiManager wifiManager; - - // Uncomment for testing wifi manager - //wifiManager.resetSettings(); - wifiManager.setAPCallback(configModeCallback); - - //or use this for auto generated name ESP + ChipID - wifiManager.autoConnect(); - - //Manual Wifi - //WiFi.begin(WIFI_SSID, WIFI_PWD); - String hostname(HOSTNAME); - hostname += String(ESP.getChipId(), HEX); - WiFi.hostname(hostname); - - int cnt = 0; - while (WiFi.status() != WL_CONNECTED) { - digitalWrite(externalLight, LOW); - delay(500); - Serial.print("."); - cnt++; - digitalWrite(externalLight, HIGH); - } - - // print the received signal strength: - Serial.print("Signal Strength (RSSI): "); - Serial.print(getWifiQuality()); - Serial.println("%"); - - if (ENABLE_OTA) { - ArduinoOTA.onStart([]() { - Serial.println("Start"); - }); - ArduinoOTA.onEnd([]() { - Serial.println("\nEnd"); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); - else if (error == OTA_END_ERROR) Serial.println("End Failed"); - }); - ArduinoOTA.begin(); - } - - - if (WEBSERVER_ENABLED) { - server.on("/", displayWeatherData); - server.on("/pull", handlePull); - server.on("/locations", handleLocations); - server.on("/systemreset", handleSystemReset); - server.on("/forgetwifi", handleForgetWifi); - server.on("/configure", handleConfigure); - server.on("/display", handleDisplay); - server.onNotFound(redirectHome); - // Start the server - server.begin(); - Serial.println("Server started"); - // Print the IP address - String webAddress = "http://" + WiFi.localIP().toString() + ":" + String(WEBSERVER_PORT) + "/"; - Serial.println("Use this URL : " + webAddress); - scrollMessage(WiFi.localIP().toString() + " "); - } else { - Serial.println("Web Interface is Disabled"); - scrollMessage("Web Interface is Disabled"); - } - - flashLED(1, 500); -} - -//************************************************************ -// Main Looop -//************************************************************ -void loop() { - //Get some Weather Data to serve - if((getMinutesFromLastRefresh() >= minutesBetweenDataRefresh) || lastEpoch == 0) { - getWeatherData(); - } - - checkDisplay(); // this will see if we need to turn it on or off for night mode. - - if (lastMinute != timeClient.getMinutes()) { - if (displayOn) { - matrix.shutdown(false); - } - matrix.fillScreen(LOW); // show black - if (OCTOPRINT_ENABLED) { - if (displayOn && ((printerClient.isOperational() || printerClient.isPrinting()) || printerCount == 0)) { - // This should only get called if the printer is actually running or if it has been 5 minutes - printerClient.getPrinterJobResults(); - } - printerCount += 1; - if (printerCount > 5) { - printerCount = 0; - } - } - - lastMinute = timeClient.getMinutes(); - String temperature = weatherClient.getTemp(0); - if ((temperature.indexOf(".") != -1) && (temperature.length() >= (temperature.indexOf(".") + 2))) { - temperature.remove(temperature.indexOf(".") + 2); - } - String description = weatherClient.getDescription(0); - description.toUpperCase(); - String msg; - if (OCTOPRINT_ENABLED && printerClient.isPrinting()) { - msg += printerClient.getFileName() + " "; - msg += "(" + printerClient.getProgressCompletion() + "%) "; - } - msg += weatherClient.getCity(0) + " "; - msg += temperature + getTempSymbol() + " "; - msg += description + " "; - msg += "Humidity:" + weatherClient.getHumidity(0) + "% "; - msg += "Wind:" + weatherClient.getWind(0) + " "; - msg += marqueeMessage + " "; - if (NEWS_ENABLED) { - msg += " " + NEWS_SOURCE + ": " + newsClient.getTitle(newsIndex) + " "; - newsIndex += 1; - if (newsIndex > 9) { - newsIndex = 0; - } - } - if (ADVICE_ENABLED) { - msg += " Advice: " + adviceClient.getAdvice() + " "; - } - scrollMessage(msg); - } - - String hourMinutes = timeClient.getAmPmHours() + ":" + timeClient.getMinutes(); - if (IS_24HOUR) { - hourMinutes = timeClient.getHours() + ":" + timeClient.getMinutes(); - } - centerPrint(hourMinutes); - - if (WEBSERVER_ENABLED) { - server.handleClient(); - } - if (ENABLE_OTA) { - ArduinoOTA.handle(); - } -} - -boolean athentication() { - if (IS_BASIC_AUTH) { - return server.authenticate(www_username, www_password); - } - return true; // Authentication not required -} - -void handlePull() { - timeOffsetFetched = false; - getWeatherData(); // this will force a data pull for new weather - displayWeatherData(); -} - -void handleLocations() { - if (!athentication()) { - return server.requestAuthentication(); - } - CityIDs[0] = server.arg("city1").toInt(); - NEWS_ENABLED = server.hasArg("displaynews"); - ADVICE_ENABLED = server.hasArg("displayadvice"); - IS_24HOUR = server.hasArg("is24hour"); - IS_METRIC = server.hasArg("metric"); - NEWS_SOURCE = server.arg("newssource"); - marqueeMessage = decodeHtmlString(server.arg("marqueeMsg")); - timeDisplayTurnsOn = decodeHtmlString(server.arg("startTime")); - timeDisplayTurnsOff = decodeHtmlString(server.arg("endTime")); - displayIntensity = server.arg("ledintensity").toInt(); - minutesBetweenDataRefresh = server.arg("refresh").toInt(); - OCTOPRINT_ENABLED = server.hasArg("displayoctoprint"); - OctoPrintApiKey = server.arg("octoPrintApiKey"); - OctoPrintServer = server.arg("octoPrintAddress"); - OctoPrintPort = server.arg("octoPrintPort").toInt(); - IS_BASIC_AUTH = server.hasArg("isBasicAuth"); - String temp = server.arg("userid"); - temp.toCharArray(www_username, sizeof(temp)); - temp = server.arg("stationpassword"); - temp.toCharArray(www_password, sizeof(temp)); - weatherClient.setMetric(IS_METRIC); - matrix.fillScreen(LOW); // show black - writeCityIds(); - getWeatherData(); // this will force a data pull for new weather - redirectHome(); -} - -void handleSystemReset() { - if (!athentication()) { - return server.requestAuthentication(); - } - Serial.println("Reset System Configuration"); - if (SPIFFS.remove(CONFIG)) { - redirectHome(); - ESP.restart(); - } -} - -void handleForgetWifi() { - if (!athentication()) { - return server.requestAuthentication(); - } - //WiFiManager - //Local intialization. Once its business is done, there is no need to keep it around - redirectHome(); - WiFiManager wifiManager; - wifiManager.resetSettings(); - ESP.restart(); -} - -void handleConfigure() { - if (!athentication()) { - return server.requestAuthentication(); - } - digitalWrite(externalLight, LOW); - String html = ""; - - server.sendHeader("Cache-Control", "no-cache, no-store"); - server.sendHeader("Pragma", "no-cache"); - server.sendHeader("Expires", "-1"); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send(200, "text/html", ""); - - html = getHeader(); - server.sendContent(html); - - String form = String(CHANGE_FORM1); - for (int inx = 0; inx < 1; inx++) { - String cityName = ""; - if (CityIDs[inx] > 0) { - cityName = weatherClient.getCity(inx) + ", " + weatherClient.getCountry(inx); - } else { - cityName = "Available"; - } - form.replace(String("%CITYNAME" + String(inx +1) + "%"), cityName); - form.replace(String("%CITY" + String(inx +1) + "%"), String(CityIDs[inx])); - } - String is24hourChecked = ""; - if (IS_24HOUR) { - is24hourChecked = "checked='checked'"; - } - form.replace("%IS_24HOUR_CHECKED%", is24hourChecked); - String checked = ""; - if (IS_METRIC) { - checked = "checked='checked'"; - } - form.replace("%CHECKED%", checked); - String isNewsDisplayedChecked = ""; - if (NEWS_ENABLED) { - isNewsDisplayedChecked = "checked='checked'"; - } - form.replace("%NEWSCHECKED%", isNewsDisplayedChecked); - String newsOptions = String(NEWS_OPTIONS); - newsOptions.replace(">"+String(NEWS_SOURCE)+"<", " selected>"+String(NEWS_SOURCE)+"<"); - form.replace("%NEWSOPTIONS%", newsOptions); - server.sendContent(String(form)); //Send first Chunk of form - - form = String(CHANGE_FORM2); - String isAdviceDisplayedChecked = ""; - if (ADVICE_ENABLED) { - isAdviceDisplayedChecked = "checked='checked'"; - } - form.replace("%ADVICECHECKED%", isAdviceDisplayedChecked); - form.replace("%MSG%", marqueeMessage); - form.replace("%STARTTIME%", timeDisplayTurnsOn); - form.replace("%ENDTIME%", timeDisplayTurnsOff); - String ledOptions = ""; - ledOptions.replace(">"+String(displayIntensity)+"<", " selected>"+String(displayIntensity)+"<"); - form.replace("%INTENSITYOPTIONS%", ledOptions); - String options = ""; - options.replace(">"+String(minutesBetweenDataRefresh)+"<", " selected>"+String(minutesBetweenDataRefresh)+"<"); - form.replace("%OPTIONS%", options); - String isOctoPrintDisplayedChecked = ""; - if (OCTOPRINT_ENABLED) { - isOctoPrintDisplayedChecked = "checked='checked'"; - } - form.replace("%OCTOCHECKED%", isOctoPrintDisplayedChecked); - form.replace("%OCTOKEY%", OctoPrintApiKey); - form.replace("%OCTOADDRESS%", OctoPrintServer); - form.replace("%OCTOPORT%", String(OctoPrintPort)); - String isUseSecurityChecked = ""; - if (IS_BASIC_AUTH) { - isUseSecurityChecked = "checked='checked'"; - } - form.replace("%IS_BASICAUTH_CHECKED%", isUseSecurityChecked); - form.replace("%USERID%", String(www_username)); - form.replace("%STATIONPASSWORD%", String(www_password)); - - server.sendContent(String(form)); // Send the second chunk of Data - - html = getFooter(); - server.sendContent(html); - server.sendContent(""); - server.client().stop(); - digitalWrite(externalLight, HIGH); -} - -void handleDisplay() { - if (!athentication()) { - return server.requestAuthentication(); - } - enableDisplay(!displayOn); - String state = "OFF"; - if (displayOn) { - state = "ON"; - } - displayMessage("Display is now " + state); -} - -//*********************************************************************** -void getWeatherData() //client function to send/receive GET request data. -{ - digitalWrite(externalLight, LOW); - matrix.fillScreen(LOW); // show black - Serial.println(); - - if (displayOn) { - // only pull the weather data if display is on - centerPrint("."); - weatherClient.updateWeather(); - if (weatherClient.getError() != "") { - do { - scrollMessage(weatherClient.getError()); - } while (true); - } - } - - Serial.println("Updating Time..."); - //Update the Time - centerPrint(".."); - timeClient.updateTime(); - lastEpoch = timeClient.getCurrentEpoch(); - if (firstEpoch == 0) { - // This is the first time running record the time - String curDate = weatherClient.getDt(0); - curDate = curDate.substring(0, curDate.length() - String(lastEpoch).length()); - curDate = curDate + String(lastEpoch); - firstEpoch = curDate.toInt(); - Serial.println("firstEpoch is: " + String(firstEpoch)); - } - - if (NEWS_ENABLED && displayOn) { - centerPrint("..."); - Serial.println("Getting News Data for " + NEWS_SOURCE); - newsClient.updateNews(); - } - - if (ADVICE_ENABLED && displayOn) { - centerPrint("..."); - Serial.println("Getting some Advice"); - adviceClient.updateAdvice(); - } - - if (!timeOffsetFetched) { - // we need to get offsets - centerPrint("...."); - timeOffsetFetched = true; - GeoNamesClient geoNames(GEONAMES_USER, weatherClient.getLat(0), weatherClient.getLon(0)); - UtcOffset = geoNames.getTimeOffset(); - } - - matrix.fillScreen(LOW); // show black - Serial.println("Version: " + String(VERSION)); - Serial.println(); - digitalWrite(externalLight, HIGH); -} - -void displayMessage(String message) { - digitalWrite(externalLight, LOW); - - server.sendHeader("Cache-Control", "no-cache, no-store"); - server.sendHeader("Pragma", "no-cache"); - server.sendHeader("Expires", "-1"); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send(200, "text/html", ""); - String html = getHeader(); - server.sendContent(String(html)); - server.sendContent(String(message)); - html = getFooter(); - server.sendContent(String(html)); - server.sendContent(""); - server.client().stop(); - - digitalWrite(externalLight, HIGH); -} - -void redirectHome() { - // Send them back to the Root Directory - server.sendHeader("Location", String("/"), true); - server.sendHeader("Cache-Control", "no-cache, no-store"); - server.sendHeader("Pragma", "no-cache"); - server.sendHeader("Expires", "-1"); - server.send(302, "text/plain", ""); - server.client().stop(); -} - -String getHeader() { - String menu = String(WEB_ACTIONS); - menu.replace("%TOGGLEDISPLAY%", (displayOn) ? " Turn Display OFF" : " Turn Display ON"); - String html = ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "

Weather Marquee

"; - html += ""; - html += "
"; - return html; -} - -String getFooter() { - int8_t rssi = getWifiQuality(); - Serial.print("Signal Strength (RSSI): "); - Serial.print(rssi); - Serial.println("%"); - String html = "


"; - html += "
"; - html += ""; - html += ""; - return html; -} - -void displayWeatherData() { - digitalWrite(externalLight, LOW); - String html = ""; - - server.sendHeader("Cache-Control", "no-cache, no-store"); - server.sendHeader("Pragma", "no-cache"); - server.sendHeader("Expires", "-1"); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send(200, "text/html", ""); - server.sendContent(String(getHeader())); - - for (int inx = 0; inx < 1; inx++) { - if (weatherClient.getTemp(inx) == "") { - break; // no more data - } - String temperature = weatherClient.getTemp(inx); - - if ((temperature.indexOf(".") != -1) && (temperature.length() >= (temperature.indexOf(".") + 2))) { - temperature.remove(temperature.indexOf(".") + 2); - } - - timeClient.setUtcOffset(getTimeOffset(inx)); - String time = timeClient.getAmPmFormattedTime(); - - Serial.println(weatherClient.getCity(inx)); - Serial.println(weatherClient.getCondition(inx)); - Serial.println(weatherClient.getDescription(inx)); - Serial.println(temperature); - Serial.println(time); - - html += "

" + weatherClient.getCity(inx) + ", " + weatherClient.getCountry(inx) + "

"; - html += "
"; - html += "" + weatherClient.getDescription(inx) + "
"; - html += weatherClient.getHumidity(inx) + "% Humidity
"; - html += weatherClient.getWind(inx) + " mph Wind
"; - html += "
"; - html += "

"; - html += weatherClient.getCondition(inx) + " (" + weatherClient.getDescription(inx) + ")
"; - html += temperature + " " + getTempSymbol() + "
"; - html += time + "
"; - html += " Map It!
"; - html += "


"; - - server.sendContent(String(html)); // spit out what we got - html = ""; // fresh start - } - - if (OCTOPRINT_ENABLED) { - html = "
OctoPrint Status: "; - if (printerClient.isPrinting()) { - html += printerClient.getState() + " " + printerClient.getFileName() + " (" + printerClient.getProgressCompletion() + "%)"; - } else if (printerClient.isOperational()) { - html += printerClient.getState(); - } else { - html += "Not Opperational"; - } - html += "


"; - server.sendContent(String(html)); - html = ""; - } - - if (NEWS_ENABLED) { - html = "

News (" + NEWS_SOURCE + ")

"; - for (int inx = 0; inx < 10; inx++) { - html += "
" + newsClient.getTitle(inx) + "
"; - html += "
" + newsClient.getDescription(inx) + "

"; - server.sendContent(String(html)); - html = ""; - } - } - - if (ADVICE_ENABLED) { - html = "

Advice Slip

"; - html += "
Current Advice:
"; - html += "
" + adviceClient.getAdvice() + "

"; - server.sendContent(String(html)); - html = ""; - } - - server.sendContent(String(getFooter())); - server.sendContent(""); - server.client().stop(); - digitalWrite(externalLight, HIGH); -} - -float getTimeOffset(int index) { - if (timeOffsetFetched) { - return UtcOffset; - } - // we need to get offsets - timeOffsetFetched = true; - - GeoNamesClient geoNames(GEONAMES_USER, weatherClient.getLat(0), weatherClient.getLon(0)); - UtcOffset = geoNames.getTimeOffset(); - - return UtcOffset; -} - -void configModeCallback (WiFiManager *myWiFiManager) { - Serial.println("Entered config mode"); - Serial.println(WiFi.softAPIP()); - Serial.println("Wifi Manager"); - Serial.println("Please connect to AP"); - Serial.println(myWiFiManager->getConfigPortalSSID()); - Serial.println("To setup Wifi Configuration"); - scrollMessage("Please Connect to AP: " + String(myWiFiManager->getConfigPortalSSID())); - centerPrint("wifi"); -} - -void flashLED(int number, int delayTime) { - for (int inx = 0; inx < number; inx++) { - tone(BUZZER_PIN, 440, delayTime); - delay(delayTime); - digitalWrite(externalLight, LOW); - delay(delayTime); - digitalWrite(externalLight, HIGH); - delay(delayTime); - } - noTone(BUZZER_PIN); -} - -String getTempSymbol() { - String rtnValue = "F"; - if (IS_METRIC) { - rtnValue = "C"; - } - return rtnValue; -} - -// converts the dBm to a range between 0 and 100% -int8_t getWifiQuality() { - int32_t dbm = WiFi.RSSI(); - if(dbm <= -100) { - return 0; - } else if(dbm >= -50) { - return 100; - } else { - return 2 * (dbm + 100); - } -} - -String getTimeTillUpdate() { - String rtnValue = ""; - - long timeToUpdate = (((minutesBetweenDataRefresh * 60) + lastEpoch) - timeClient.getCurrentEpoch()); - - int hours = numberOfHours(timeToUpdate); - int minutes = numberOfMinutes(timeToUpdate); - int seconds = numberOfSeconds(timeToUpdate); - - rtnValue += String(hours) + ":"; - if (minutes < 10) { - rtnValue += "0"; - } - rtnValue += String(minutes) + ":"; - if (seconds <10) { - rtnValue += "0"; - } - rtnValue += String(seconds); - - return rtnValue; -} - -int getMinutesFromLastRefresh() { - int minutes = (timeClient.getCurrentEpoch() - lastEpoch) / 60; - return minutes; -} - -int getMinutesFromLastDisplay() { - int minutes = (timeClient.getCurrentEpoch() - displayOffEpoch) / 60; - return minutes; -} - -void enableDisplay(boolean enable) { - displayOn = enable; - if (enable) { - if (getMinutesFromLastDisplay() >= minutesBetweenDataRefresh) { - // The display has been off longer than the minutes between refresh -- need to get fresh data - lastEpoch = 0; // this should force a data pull of the weather - displayOffEpoch = 0; // reset - } - matrix.shutdown(false); - matrix.fillScreen(LOW); // show black - Serial.println("Display was turned ON: " + timeClient.getFormattedTime()); - } else { - matrix.shutdown(true); - Serial.println("Display was turned OFF: " + timeClient.getFormattedTime()); - displayOffEpoch = lastEpoch; - } -} - -// Toggle on and off the display if user defined times -void checkDisplay() { - if (timeDisplayTurnsOn == "" || timeDisplayTurnsOff == "") { - return; // nothing to do - } - timeClient.setUtcOffset(getTimeOffset(0)); - String currentTime = timeClient.getHours() + ":" + timeClient.getMinutes(); - - if (currentTime == timeDisplayTurnsOn && !displayOn) { - Serial.println("Time to turn display on: " + currentTime); - flashLED(1, 500); - enableDisplay(true); - } - - if (currentTime == timeDisplayTurnsOff && displayOn) { - Serial.println("Time to turn display off: " + currentTime); - flashLED(2, 500); - enableDisplay(false); - } -} - -String writeCityIds() { - // Save decoded message to SPIFFS file for playback on power up. - File f = SPIFFS.open(CONFIG, "w"); - if (!f) { - Serial.println("File open failed!"); - } else { - Serial.println("Saving settings now..."); - f.println("CityID=" + String(CityIDs[0])); - f.println("marqueeMessage=" + marqueeMessage); - f.println("newsSource=" + NEWS_SOURCE); - f.println("timeDisplayTurnsOn=" + timeDisplayTurnsOn); - f.println("timeDisplayTurnsOff=" + timeDisplayTurnsOff); - f.println("ledIntensity=" + String(displayIntensity)); - f.println("isNews=" + String(NEWS_ENABLED)); - f.println("isAdvice=" + String(ADVICE_ENABLED)); - f.println("is24hour=" + String(IS_24HOUR)); - f.println("isMetric=" + String(IS_METRIC)); - f.println("refreshRate=" + String(minutesBetweenDataRefresh)); - f.println("isOctoPrint=" + String(OCTOPRINT_ENABLED)); - f.println("octoKey=" + OctoPrintApiKey); - f.println("octoServer=" + OctoPrintServer); - f.println("octoPort=" + String(OctoPrintPort)); - f.println("www_username=" + String(www_username)); - f.println("www_password=" + String(www_password)); - f.println("IS_BASIC_AUTH=" + String(IS_BASIC_AUTH)); - } - f.close(); - readCityIds(); - timeOffsetFetched = false; - weatherClient.updateCityIdList(CityIDs, 1); - String cityIds = weatherClient.getMyCityIDs(); - return cityIds; -} - -void readCityIds() { - if (SPIFFS.exists(CONFIG) == false) { - Serial.println("Settings File does not yet exists."); - writeCityIds(); - return; - } - File fr = SPIFFS.open(CONFIG, "r"); - String line; - while(fr.available()) { - line = fr.readStringUntil('\n'); - if (line.indexOf("CityID=") >= 0) { - CityIDs[0] = line.substring(line.lastIndexOf("CityID=") + 7).toInt(); - Serial.println("CityID: " + String(CityIDs[0])); - } - if (line.indexOf("newsSource=") >= 0) { - NEWS_SOURCE = line.substring(line.lastIndexOf("newsSource=") + 11); - NEWS_SOURCE.trim(); - Serial.println("newsSource=" + NEWS_SOURCE); - } - if (line.indexOf("isNews=") >= 0) { - NEWS_ENABLED = line.substring(line.lastIndexOf("isNews=") + 7).toInt(); - Serial.println("NEWS_ENABLED=" + String(NEWS_ENABLED)); - } - if (line.indexOf("is24hour=") >= 0) { - IS_24HOUR = line.substring(line.lastIndexOf("is24hour=") + 9).toInt(); - Serial.println("IS_24HOUR=" + String(IS_24HOUR)); - } - if (line.indexOf("isMetric=") >= 0) { - IS_METRIC = line.substring(line.lastIndexOf("isMetric=") + 9).toInt(); - Serial.println("IS_METRIC=" + String(IS_METRIC)); - } - if (line.indexOf("refreshRate=") >= 0) { - minutesBetweenDataRefresh = line.substring(line.lastIndexOf("refreshRate=") + 12).toInt(); - Serial.println("minutesBetweenDataRefresh=" + String(minutesBetweenDataRefresh)); - } - if (line.indexOf("marqueeMessage=") >= 0) { - marqueeMessage = line.substring(line.lastIndexOf("marqueeMessage=") + 15); - marqueeMessage.trim(); - Serial.println("marqueeMessage=" + marqueeMessage); - } - if (line.indexOf("timeDisplayTurnsOn=") >= 0) { - timeDisplayTurnsOn = line.substring(line.lastIndexOf("timeDisplayTurnsOn=") + 19); - timeDisplayTurnsOn.trim(); - Serial.println("timeDisplayTurnsOn=" + timeDisplayTurnsOn); - } - if (line.indexOf("timeDisplayTurnsOff=") >= 0) { - timeDisplayTurnsOff = line.substring(line.lastIndexOf("timeDisplayTurnsOff=") + 20); - timeDisplayTurnsOff.trim(); - Serial.println("timeDisplayTurnsOff=" + timeDisplayTurnsOff); - } - if (line.indexOf("ledIntensity=") >= 0) { - displayIntensity = line.substring(line.lastIndexOf("ledIntensity=") + 13).toInt(); - Serial.println("displayIntensity=" + String(displayIntensity)); - } - if (line.indexOf("isOctoPrint=") >= 0) { - OCTOPRINT_ENABLED = line.substring(line.lastIndexOf("isOctoPrint=") + 12).toInt(); - Serial.println("OCTOPRINT_ENABLED=" + String(OCTOPRINT_ENABLED)); - } - if (line.indexOf("octoKey=") >= 0) { - OctoPrintApiKey = line.substring(line.lastIndexOf("octoKey=") + 8); - OctoPrintApiKey.trim(); - Serial.println("OctoPrintApiKey=" + OctoPrintApiKey); - } - if (line.indexOf("octoServer=") >= 0) { - OctoPrintServer = line.substring(line.lastIndexOf("octoServer=") + 11); - OctoPrintServer.trim(); - Serial.println("OctoPrintServer=" + OctoPrintServer); - } - if (line.indexOf("octoPort=") >= 0) { - OctoPrintPort = line.substring(line.lastIndexOf("octoPort=") + 9).toInt(); - Serial.println("OctoPrintPort=" + String(OctoPrintPort)); - } - if (line.indexOf("www_username=") >= 0) { - String temp = line.substring(line.lastIndexOf("www_username=") + 13); - temp.trim(); - temp.toCharArray(www_username, sizeof(temp)); - Serial.println("www_username=" + String(www_username)); - } - if (line.indexOf("www_password=") >= 0) { - String temp = line.substring(line.lastIndexOf("www_password=") + 13); - temp.trim(); - temp.toCharArray(www_password, sizeof(temp)); - Serial.println("www_password=" + String(www_password)); - } - if (line.indexOf("IS_BASIC_AUTH=") >= 0) { - IS_BASIC_AUTH = line.substring(line.lastIndexOf("IS_BASIC_AUTH=") + 14).toInt(); - Serial.println("IS_BASIC_AUTH=" + String(IS_BASIC_AUTH)); - } - } - fr.close(); - matrix.setIntensity(displayIntensity); - newsClient.updateNewsSource(NEWS_SOURCE); - weatherClient.setMetric(IS_METRIC); - weatherClient.updateCityIdList(CityIDs, 1); - printerClient.updateOctoPrintClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); - if (OCTOPRINT_ENABLED) { - printerClient.getPrinterJobResults(); - } -} - -void scrollMessage(String msg) { - msg += " "; // add a space at the end - for ( int i = 0 ; i < width * msg.length() + matrix.width() - 1 - spacer; i++ ) { - if (WEBSERVER_ENABLED) { - server.handleClient(); - } - if (ENABLE_OTA) { - ArduinoOTA.handle(); - } - if (refresh==1) i=0; - refresh=0; - matrix.fillScreen(LOW); - - int letter = i / width; - int x = (matrix.width() - 1) - i % width; - int y = (matrix.height() - 8) / 2; // center the text vertically - - while ( x + width - spacer >= 0 && letter >= 0 ) { - if ( letter < msg.length() ) { - matrix.drawChar(x, y, msg[letter], HIGH, LOW, 1); - } - - letter--; - x -= width; - } - - matrix.write(); // Send bitmap to display - delay(wait); - } - matrix.setCursor(0,0); -} - -void centerPrint(String msg) { - int x = (matrix.width() - (msg.length() * width)) / 2; - matrix.setCursor(x, 0); - matrix.print(msg); - matrix.write(); -} - -String decodeHtmlString(String msg) { - String decodedMsg = msg; - // Restore special characters that are misformed to %char by the client browser - decodedMsg.replace("+", " "); - decodedMsg.replace("%21", "!"); - decodedMsg.replace("%22", ""); - decodedMsg.replace("%23", "#"); - decodedMsg.replace("%24", "$"); - decodedMsg.replace("%25", "%"); - decodedMsg.replace("%26", "&"); - decodedMsg.replace("%27", "'"); - decodedMsg.replace("%28", "("); - decodedMsg.replace("%29", ")"); - decodedMsg.replace("%2A", "*"); - decodedMsg.replace("%2B", "+"); - decodedMsg.replace("%2C", ","); - decodedMsg.replace("%2F", "/"); - decodedMsg.replace("%3A", ":"); - decodedMsg.replace("%3B", ";"); - decodedMsg.replace("%3C", "<"); - decodedMsg.replace("%3D", "="); - decodedMsg.replace("%3E", ">"); - decodedMsg.replace("%3F", "?"); - decodedMsg.replace("%40", "@"); - decodedMsg.toUpperCase(); - decodedMsg.trim(); - return decodedMsg; +/** The MIT License (MIT) + +Copyright (c) 2018 David Payne + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + + /********************************************** + * Edit Settings.h for personalization + ***********************************************/ + +#include "Settings.h" + +#define VERSION "1.6" + +#define HOSTNAME "ESP8266-" +#define CONFIG "/conf.txt" +#define BUZZER_PIN D2 + +/* Useful Constants */ +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24L) + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) + +//declairing prototypes +void configModeCallback (WiFiManager *myWiFiManager); +int8_t getWifiQuality(); + +// LED Settings +const int offset = 1; +const int numberOfHorizontalDisplays = 4; +const int numberOfVerticalDisplays = 1; +int refresh = 0; +String message = "hello"; +int wait = 25; // In milliseconds -- controls speed of scroll +int spacer = 1; // dots between letters +int width = 5 + spacer; // The font width is 5 pixels + spacer +Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays); + +float UtcOffset; //time zone offsets that correspond with the CityID above (offset from GMT) + +// Time +TimeClient timeClient(UtcOffset); +String lastMinute = "xx"; +long lastEpoch = 0; +long firstEpoch = 0; +long displayOffEpoch = 0; +boolean displayOn = true; +boolean timeOffsetFetched = false; + +// News Client +NewsApiClient newsClient(NEWS_API_KEY, NEWS_SOURCE); +int newsIndex = 0; + +// Advice Client +AdviceSlipClient adviceClient; + +// Weather Client +OpenWeatherMapClient weatherClient(APIKEY, CityIDs, 1, IS_METRIC); + +// OctoPrint Client +OctoPrintClient printerClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); +int printerCount = 0; + +ESP8266WebServer server(WEBSERVER_PORT); + +const String WEB_ACTIONS = " Home" + " Configure" + " Refresh Data" + "%TOGGLEDISPLAY%" + " Reset Settings" + " Forget WiFi" + " About"; + +const String CHANGE_FORM1 = "

City ID:

" + "" + "

Search for City ID

" + " Use 24 Hour Clock (military time)

" + " Use Metric (Celsius)

" + " Display News Headlines

" + "Select News Source

"; + +const String CHANGE_FORM2 = " Display Advice

" + "" + "

" + "

" + "Select Display Brightness

" + "Refresh Data (minutes)

" + " Show OctoPrint Status

" + "" + "" + "" + "


" + " Use Security Credentials for Configuration Changes

" + "" + "" + "

" + ""; + +const String NEWS_OPTIONS = "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + +const int TIMEOUT = 500; // 500 = 1/2 second +int timeoutCount = 0; + +// Change the externalLight to the pin you wish to use if other than the Built-in LED +int externalLight = LED_BUILTIN; // LED_BUILTIN is is the built in LED on the Wemos + +void setup() { + Serial.begin(115200); + SPIFFS.begin(); + //SPIFFS.remove(CONFIG); + delay(10); + + // Initialize digital pin for LED + pinMode(externalLight, OUTPUT); + + //New Line to clear from start garbage + Serial.println(); + + readCityIds(); + + // initialize dispaly + matrix.setIntensity(0); // Use a value between 0 and 15 for brightness + matrix.setRotation(0,3); + matrix.setRotation(1,3); + matrix.setRotation(2,3); + matrix.setRotation(3,3); + + +// Adjust to your own needs + matrix.setPosition(0, 3, 0); // The first display is at <0, 7> + matrix.setPosition(1, 2, 0); // The second display is at <1, 0> + matrix.setPosition(2, 1, 0); // The third display is at <2, 0> + matrix.setPosition(3, 0, 0); // And the last display is at <3, 0> + Serial.println("matrix created"); + matrix.fillScreen(LOW); // show black + centerPrint("hello"); + + tone(BUZZER_PIN, 415, 500); + delay(500*1.3); + tone(BUZZER_PIN, 466, 500); + delay(500*1.3); + tone(BUZZER_PIN, 370, 1000); + delay(1000*1.3); + noTone(BUZZER_PIN); + + for (int inx = 0; inx <= 15; inx++) { + matrix.setIntensity(inx); + delay(100); + } + for (int inx = 15; inx > 0; inx--) { + matrix.setIntensity(inx); + delay(60); + } + delay(1000); + matrix.setIntensity(displayIntensity); + //noTone(BUZZER_PIN); + + //WiFiManager + //Local intialization. Once its business is done, there is no need to keep it around + WiFiManager wifiManager; + + // Uncomment for testing wifi manager + //wifiManager.resetSettings(); + wifiManager.setAPCallback(configModeCallback); + + //or use this for auto generated name ESP + ChipID + wifiManager.autoConnect(); + + //Manual Wifi + //WiFi.begin(WIFI_SSID, WIFI_PWD); + String hostname(HOSTNAME); + hostname += String(ESP.getChipId(), HEX); + WiFi.hostname(hostname); + + int cnt = 0; + while (WiFi.status() != WL_CONNECTED) { + digitalWrite(externalLight, LOW); + delay(500); + Serial.print("."); + cnt++; + digitalWrite(externalLight, HIGH); + } + + // print the received signal strength: + Serial.print("Signal Strength (RSSI): "); + Serial.print(getWifiQuality()); + Serial.println("%"); + + if (ENABLE_OTA) { + ArduinoOTA.onStart([]() { + Serial.println("Start"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + } + + + if (WEBSERVER_ENABLED) { + server.on("/", displayWeatherData); + server.on("/pull", handlePull); + server.on("/locations", handleLocations); + server.on("/systemreset", handleSystemReset); + server.on("/forgetwifi", handleForgetWifi); + server.on("/configure", handleConfigure); + server.on("/display", handleDisplay); + server.onNotFound(redirectHome); + // Start the server + server.begin(); + Serial.println("Server started"); + // Print the IP address + String webAddress = "http://" + WiFi.localIP().toString() + ":" + String(WEBSERVER_PORT) + "/"; + Serial.println("Use this URL : " + webAddress); + scrollMessage(WiFi.localIP().toString() + " "); + } else { + Serial.println("Web Interface is Disabled"); + scrollMessage("Web Interface is Disabled"); + } + + flashLED(1, 500); +} + +//************************************************************ +// Main Looop +//************************************************************ +void loop() { + //Get some Weather Data to serve + if((getMinutesFromLastRefresh() >= minutesBetweenDataRefresh) || lastEpoch == 0) { + getWeatherData(); + } + + checkDisplay(); // this will see if we need to turn it on or off for night mode. + + if (lastMinute != timeClient.getMinutes()) { + if (displayOn) { + matrix.shutdown(false); + } + matrix.fillScreen(LOW); // show black + if (OCTOPRINT_ENABLED) { + if (displayOn && ((printerClient.isOperational() || printerClient.isPrinting()) || printerCount == 0)) { + // This should only get called if the printer is actually running or if it has been 5 minutes + printerClient.getPrinterJobResults(); + } + printerCount += 1; + if (printerCount > 5) { + printerCount = 0; + } + } + + lastMinute = timeClient.getMinutes(); + String temperature = weatherClient.getTemp(0); + if ((temperature.indexOf(".") != -1) && (temperature.length() >= (temperature.indexOf(".") + 2))) { + temperature.remove(temperature.indexOf(".") + 2); + } + String description = weatherClient.getDescription(0); + description.toUpperCase(); + String msg; + if (OCTOPRINT_ENABLED && printerClient.isPrinting()) { + msg += printerClient.getFileName() + " "; + msg += "(" + printerClient.getProgressCompletion() + "%) "; + } + msg += weatherClient.getCity(0) + " "; + msg += temperature + getTempSymbol() + " "; + msg += description + " "; + msg += "Humidity:" + weatherClient.getHumidity(0) + "% "; + msg += "Wind:" + weatherClient.getWind(0) + " "; + msg += marqueeMessage + " "; + if (NEWS_ENABLED) { + msg += " " + NEWS_SOURCE + ": " + newsClient.getTitle(newsIndex) + " "; + newsIndex += 1; + if (newsIndex > 9) { + newsIndex = 0; + } + } + if (ADVICE_ENABLED) { + msg += " Advice: " + adviceClient.getAdvice() + " "; + } + scrollMessage(msg); + } + + String hourMinutes = timeClient.getAmPmHours() + ":" + timeClient.getMinutes(); + if (IS_24HOUR) { + hourMinutes = timeClient.getHours() + ":" + timeClient.getMinutes(); + } + centerPrint(hourMinutes); + + if (WEBSERVER_ENABLED) { + server.handleClient(); + } + if (ENABLE_OTA) { + ArduinoOTA.handle(); + } +} + +boolean athentication() { + if (IS_BASIC_AUTH) { + return server.authenticate(www_username, www_password); + } + return true; // Authentication not required +} + +void handlePull() { + timeOffsetFetched = false; + getWeatherData(); // this will force a data pull for new weather + displayWeatherData(); +} + +void handleLocations() { + if (!athentication()) { + return server.requestAuthentication(); + } + CityIDs[0] = server.arg("city1").toInt(); + NEWS_ENABLED = server.hasArg("displaynews"); + ADVICE_ENABLED = server.hasArg("displayadvice"); + IS_24HOUR = server.hasArg("is24hour"); + IS_METRIC = server.hasArg("metric"); + NEWS_SOURCE = server.arg("newssource"); + marqueeMessage = decodeHtmlString(server.arg("marqueeMsg")); + timeDisplayTurnsOn = decodeHtmlString(server.arg("startTime")); + timeDisplayTurnsOff = decodeHtmlString(server.arg("endTime")); + displayIntensity = server.arg("ledintensity").toInt(); + minutesBetweenDataRefresh = server.arg("refresh").toInt(); + OCTOPRINT_ENABLED = server.hasArg("displayoctoprint"); + OctoPrintApiKey = server.arg("octoPrintApiKey"); + OctoPrintServer = server.arg("octoPrintAddress"); + OctoPrintPort = server.arg("octoPrintPort").toInt(); + IS_BASIC_AUTH = server.hasArg("isBasicAuth"); + String temp = server.arg("userid"); + temp.toCharArray(www_username, sizeof(temp)); + temp = server.arg("stationpassword"); + temp.toCharArray(www_password, sizeof(temp)); + weatherClient.setMetric(IS_METRIC); + matrix.fillScreen(LOW); // show black + writeCityIds(); + getWeatherData(); // this will force a data pull for new weather + redirectHome(); +} + +void handleSystemReset() { + if (!athentication()) { + return server.requestAuthentication(); + } + Serial.println("Reset System Configuration"); + if (SPIFFS.remove(CONFIG)) { + redirectHome(); + ESP.restart(); + } +} + +void handleForgetWifi() { + if (!athentication()) { + return server.requestAuthentication(); + } + //WiFiManager + //Local intialization. Once its business is done, there is no need to keep it around + redirectHome(); + WiFiManager wifiManager; + wifiManager.resetSettings(); + ESP.restart(); +} + +void handleConfigure() { + if (!athentication()) { + return server.requestAuthentication(); + } + digitalWrite(externalLight, LOW); + String html = ""; + + server.sendHeader("Cache-Control", "no-cache, no-store"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/html", ""); + + html = getHeader(); + server.sendContent(html); + + String form = String(CHANGE_FORM1); + for (int inx = 0; inx < 1; inx++) { + String cityName = ""; + if (CityIDs[inx] > 0) { + cityName = weatherClient.getCity(inx) + ", " + weatherClient.getCountry(inx); + } else { + cityName = "Available"; + } + form.replace(String("%CITYNAME" + String(inx +1) + "%"), cityName); + form.replace(String("%CITY" + String(inx +1) + "%"), String(CityIDs[inx])); + } + String is24hourChecked = ""; + if (IS_24HOUR) { + is24hourChecked = "checked='checked'"; + } + form.replace("%IS_24HOUR_CHECKED%", is24hourChecked); + String checked = ""; + if (IS_METRIC) { + checked = "checked='checked'"; + } + form.replace("%CHECKED%", checked); + String isNewsDisplayedChecked = ""; + if (NEWS_ENABLED) { + isNewsDisplayedChecked = "checked='checked'"; + } + form.replace("%NEWSCHECKED%", isNewsDisplayedChecked); + String newsOptions = String(NEWS_OPTIONS); + newsOptions.replace(">"+String(NEWS_SOURCE)+"<", " selected>"+String(NEWS_SOURCE)+"<"); + form.replace("%NEWSOPTIONS%", newsOptions); + server.sendContent(String(form)); //Send first Chunk of form + + form = String(CHANGE_FORM2); + String isAdviceDisplayedChecked = ""; + if (ADVICE_ENABLED) { + isAdviceDisplayedChecked = "checked='checked'"; + } + form.replace("%ADVICECHECKED%", isAdviceDisplayedChecked); + form.replace("%MSG%", marqueeMessage); + form.replace("%STARTTIME%", timeDisplayTurnsOn); + form.replace("%ENDTIME%", timeDisplayTurnsOff); + String ledOptions = ""; + ledOptions.replace(">"+String(displayIntensity)+"<", " selected>"+String(displayIntensity)+"<"); + form.replace("%INTENSITYOPTIONS%", ledOptions); + String options = ""; + options.replace(">"+String(minutesBetweenDataRefresh)+"<", " selected>"+String(minutesBetweenDataRefresh)+"<"); + form.replace("%OPTIONS%", options); + String isOctoPrintDisplayedChecked = ""; + if (OCTOPRINT_ENABLED) { + isOctoPrintDisplayedChecked = "checked='checked'"; + } + form.replace("%OCTOCHECKED%", isOctoPrintDisplayedChecked); + form.replace("%OCTOKEY%", OctoPrintApiKey); + form.replace("%OCTOADDRESS%", OctoPrintServer); + form.replace("%OCTOPORT%", String(OctoPrintPort)); + String isUseSecurityChecked = ""; + if (IS_BASIC_AUTH) { + isUseSecurityChecked = "checked='checked'"; + } + form.replace("%IS_BASICAUTH_CHECKED%", isUseSecurityChecked); + form.replace("%USERID%", String(www_username)); + form.replace("%STATIONPASSWORD%", String(www_password)); + + server.sendContent(String(form)); // Send the second chunk of Data + + html = getFooter(); + server.sendContent(html); + server.sendContent(""); + server.client().stop(); + digitalWrite(externalLight, HIGH); +} + +void handleDisplay() { + if (!athentication()) { + return server.requestAuthentication(); + } + enableDisplay(!displayOn); + String state = "OFF"; + if (displayOn) { + state = "ON"; + } + displayMessage("Display is now " + state); +} + +//*********************************************************************** +void getWeatherData() //client function to send/receive GET request data. +{ + digitalWrite(externalLight, LOW); + matrix.fillScreen(LOW); // show black + Serial.println(); + + if (displayOn) { + // only pull the weather data if display is on + centerPrint("."); + weatherClient.updateWeather(); + if (weatherClient.getError() != "") { + do { + scrollMessage(weatherClient.getError()); + } while (true); + } + } + + Serial.println("Updating Time..."); + //Update the Time + centerPrint(".."); + timeClient.updateTime(); + lastEpoch = timeClient.getCurrentEpoch(); + if (firstEpoch == 0) { + // This is the first time running record the time + String curDate = weatherClient.getDt(0); + curDate = curDate.substring(0, curDate.length() - String(lastEpoch).length()); + curDate = curDate + String(lastEpoch); + firstEpoch = curDate.toInt(); + Serial.println("firstEpoch is: " + String(firstEpoch)); + } + + if (NEWS_ENABLED && displayOn) { + centerPrint("..."); + Serial.println("Getting News Data for " + NEWS_SOURCE); + newsClient.updateNews(); + } + + if (ADVICE_ENABLED && displayOn) { + centerPrint("..."); + Serial.println("Getting some Advice"); + adviceClient.updateAdvice(); + } + + if (!timeOffsetFetched) { + // we need to get offsets + centerPrint("...."); + timeOffsetFetched = true; + GeoNamesClient geoNames(GEONAMES_USER, weatherClient.getLat(0), weatherClient.getLon(0)); + UtcOffset = geoNames.getTimeOffset(); + } + + matrix.fillScreen(LOW); // show black + Serial.println("Version: " + String(VERSION)); + Serial.println(); + digitalWrite(externalLight, HIGH); +} + +void displayMessage(String message) { + digitalWrite(externalLight, LOW); + + server.sendHeader("Cache-Control", "no-cache, no-store"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/html", ""); + String html = getHeader(); + server.sendContent(String(html)); + server.sendContent(String(message)); + html = getFooter(); + server.sendContent(String(html)); + server.sendContent(""); + server.client().stop(); + + digitalWrite(externalLight, HIGH); +} + +void redirectHome() { + // Send them back to the Root Directory + server.sendHeader("Location", String("/"), true); + server.sendHeader("Cache-Control", "no-cache, no-store"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.send(302, "text/plain", ""); + server.client().stop(); +} + +String getHeader() { + String menu = String(WEB_ACTIONS); + menu.replace("%TOGGLEDISPLAY%", (displayOn) ? " Turn Display OFF" : " Turn Display ON"); + String html = ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "

Weather Marquee

"; + html += ""; + html += "
"; + return html; +} + +String getFooter() { + int8_t rssi = getWifiQuality(); + Serial.print("Signal Strength (RSSI): "); + Serial.print(rssi); + Serial.println("%"); + String html = "


"; + html += "
"; + html += ""; + html += ""; + return html; +} + +void displayWeatherData() { + digitalWrite(externalLight, LOW); + String html = ""; + + server.sendHeader("Cache-Control", "no-cache, no-store"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/html", ""); + server.sendContent(String(getHeader())); + + for (int inx = 0; inx < 1; inx++) { + if (weatherClient.getTemp(inx) == "") { + break; // no more data + } + String temperature = weatherClient.getTemp(inx); + + if ((temperature.indexOf(".") != -1) && (temperature.length() >= (temperature.indexOf(".") + 2))) { + temperature.remove(temperature.indexOf(".") + 2); + } + + timeClient.setUtcOffset(getTimeOffset(inx)); + String time = timeClient.getAmPmFormattedTime(); + + Serial.println(weatherClient.getCity(inx)); + Serial.println(weatherClient.getCondition(inx)); + Serial.println(weatherClient.getDescription(inx)); + Serial.println(temperature); + Serial.println(time); + + html += "

" + weatherClient.getCity(inx) + ", " + weatherClient.getCountry(inx) + "

"; + html += "
"; + html += "" + weatherClient.getDescription(inx) + "
"; + html += weatherClient.getHumidity(inx) + "% Humidity
"; + html += weatherClient.getWind(inx) + " mph Wind
"; + html += "
"; + html += "

"; + html += weatherClient.getCondition(inx) + " (" + weatherClient.getDescription(inx) + ")
"; + html += temperature + " " + getTempSymbol() + "
"; + html += time + "
"; + html += " Map It!
"; + html += "


"; + + server.sendContent(String(html)); // spit out what we got + html = ""; // fresh start + } + + if (OCTOPRINT_ENABLED) { + html = "
OctoPrint Status: "; + if (printerClient.isPrinting()) { + html += printerClient.getState() + " " + printerClient.getFileName() + " (" + printerClient.getProgressCompletion() + "%)"; + } else if (printerClient.isOperational()) { + html += printerClient.getState(); + } else { + html += "Not Opperational"; + } + html += "


"; + server.sendContent(String(html)); + html = ""; + } + + if (NEWS_ENABLED) { + html = "

News (" + NEWS_SOURCE + ")

"; + for (int inx = 0; inx < 10; inx++) { + html += "
" + newsClient.getTitle(inx) + "
"; + html += "
" + newsClient.getDescription(inx) + "

"; + server.sendContent(String(html)); + html = ""; + } + } + + if (ADVICE_ENABLED) { + html = "

Advice Slip

"; + html += "
Current Advice:
"; + html += "
" + adviceClient.getAdvice() + "

"; + server.sendContent(String(html)); + html = ""; + } + + server.sendContent(String(getFooter())); + server.sendContent(""); + server.client().stop(); + digitalWrite(externalLight, HIGH); +} + +float getTimeOffset(int index) { + if (timeOffsetFetched) { + return UtcOffset; + } + // we need to get offsets + timeOffsetFetched = true; + + GeoNamesClient geoNames(GEONAMES_USER, weatherClient.getLat(0), weatherClient.getLon(0)); + UtcOffset = geoNames.getTimeOffset(); + + return UtcOffset; +} + +void configModeCallback (WiFiManager *myWiFiManager) { + Serial.println("Entered config mode"); + Serial.println(WiFi.softAPIP()); + Serial.println("Wifi Manager"); + Serial.println("Please connect to AP"); + Serial.println(myWiFiManager->getConfigPortalSSID()); + Serial.println("To setup Wifi Configuration"); + scrollMessage("Please Connect to AP: " + String(myWiFiManager->getConfigPortalSSID())); + centerPrint("wifi"); +} + +void flashLED(int number, int delayTime) { + for (int inx = 0; inx < number; inx++) { + tone(BUZZER_PIN, 440, delayTime); + delay(delayTime); + digitalWrite(externalLight, LOW); + delay(delayTime); + digitalWrite(externalLight, HIGH); + delay(delayTime); + } + noTone(BUZZER_PIN); +} + +String getTempSymbol() { + String rtnValue = "F"; + if (IS_METRIC) { + rtnValue = "C"; + } + return rtnValue; +} + +// converts the dBm to a range between 0 and 100% +int8_t getWifiQuality() { + int32_t dbm = WiFi.RSSI(); + if(dbm <= -100) { + return 0; + } else if(dbm >= -50) { + return 100; + } else { + return 2 * (dbm + 100); + } +} + +String getTimeTillUpdate() { + String rtnValue = ""; + + long timeToUpdate = (((minutesBetweenDataRefresh * 60) + lastEpoch) - timeClient.getCurrentEpoch()); + + int hours = numberOfHours(timeToUpdate); + int minutes = numberOfMinutes(timeToUpdate); + int seconds = numberOfSeconds(timeToUpdate); + + rtnValue += String(hours) + ":"; + if (minutes < 10) { + rtnValue += "0"; + } + rtnValue += String(minutes) + ":"; + if (seconds <10) { + rtnValue += "0"; + } + rtnValue += String(seconds); + + return rtnValue; +} + +int getMinutesFromLastRefresh() { + int minutes = (timeClient.getCurrentEpoch() - lastEpoch) / 60; + return minutes; +} + +int getMinutesFromLastDisplay() { + int minutes = (timeClient.getCurrentEpoch() - displayOffEpoch) / 60; + return minutes; +} + +void enableDisplay(boolean enable) { + displayOn = enable; + if (enable) { + if (getMinutesFromLastDisplay() >= minutesBetweenDataRefresh) { + // The display has been off longer than the minutes between refresh -- need to get fresh data + lastEpoch = 0; // this should force a data pull of the weather + displayOffEpoch = 0; // reset + } + matrix.shutdown(false); + matrix.fillScreen(LOW); // show black + Serial.println("Display was turned ON: " + timeClient.getFormattedTime()); + } else { + matrix.shutdown(true); + Serial.println("Display was turned OFF: " + timeClient.getFormattedTime()); + displayOffEpoch = lastEpoch; + } +} + +// Toggle on and off the display if user defined times +void checkDisplay() { + if (timeDisplayTurnsOn == "" || timeDisplayTurnsOff == "") { + return; // nothing to do + } + timeClient.setUtcOffset(getTimeOffset(0)); + String currentTime = timeClient.getHours() + ":" + timeClient.getMinutes(); + + if (currentTime == timeDisplayTurnsOn && !displayOn) { + Serial.println("Time to turn display on: " + currentTime); + flashLED(1, 500); + enableDisplay(true); + } + + if (currentTime == timeDisplayTurnsOff && displayOn) { + Serial.println("Time to turn display off: " + currentTime); + flashLED(2, 500); + enableDisplay(false); + } +} + +String writeCityIds() { + // Save decoded message to SPIFFS file for playback on power up. + File f = SPIFFS.open(CONFIG, "w"); + if (!f) { + Serial.println("File open failed!"); + } else { + Serial.println("Saving settings now..."); + f.println("CityID=" + String(CityIDs[0])); + f.println("marqueeMessage=" + marqueeMessage); + f.println("newsSource=" + NEWS_SOURCE); + f.println("timeDisplayTurnsOn=" + timeDisplayTurnsOn); + f.println("timeDisplayTurnsOff=" + timeDisplayTurnsOff); + f.println("ledIntensity=" + String(displayIntensity)); + f.println("isNews=" + String(NEWS_ENABLED)); + f.println("isAdvice=" + String(ADVICE_ENABLED)); + f.println("is24hour=" + String(IS_24HOUR)); + f.println("isMetric=" + String(IS_METRIC)); + f.println("refreshRate=" + String(minutesBetweenDataRefresh)); + f.println("isOctoPrint=" + String(OCTOPRINT_ENABLED)); + f.println("octoKey=" + OctoPrintApiKey); + f.println("octoServer=" + OctoPrintServer); + f.println("octoPort=" + String(OctoPrintPort)); + f.println("www_username=" + String(www_username)); + f.println("www_password=" + String(www_password)); + f.println("IS_BASIC_AUTH=" + String(IS_BASIC_AUTH)); + } + f.close(); + readCityIds(); + timeOffsetFetched = false; + weatherClient.updateCityIdList(CityIDs, 1); + String cityIds = weatherClient.getMyCityIDs(); + return cityIds; +} + +void readCityIds() { + if (SPIFFS.exists(CONFIG) == false) { + Serial.println("Settings File does not yet exists."); + writeCityIds(); + return; + } + File fr = SPIFFS.open(CONFIG, "r"); + String line; + while(fr.available()) { + line = fr.readStringUntil('\n'); + if (line.indexOf("CityID=") >= 0) { + CityIDs[0] = line.substring(line.lastIndexOf("CityID=") + 7).toInt(); + Serial.println("CityID: " + String(CityIDs[0])); + } + if (line.indexOf("newsSource=") >= 0) { + NEWS_SOURCE = line.substring(line.lastIndexOf("newsSource=") + 11); + NEWS_SOURCE.trim(); + Serial.println("newsSource=" + NEWS_SOURCE); + } + if (line.indexOf("isNews=") >= 0) { + NEWS_ENABLED = line.substring(line.lastIndexOf("isNews=") + 7).toInt(); + Serial.println("NEWS_ENABLED=" + String(NEWS_ENABLED)); + } + if (line.indexOf("is24hour=") >= 0) { + IS_24HOUR = line.substring(line.lastIndexOf("is24hour=") + 9).toInt(); + Serial.println("IS_24HOUR=" + String(IS_24HOUR)); + } + if (line.indexOf("isMetric=") >= 0) { + IS_METRIC = line.substring(line.lastIndexOf("isMetric=") + 9).toInt(); + Serial.println("IS_METRIC=" + String(IS_METRIC)); + } + if (line.indexOf("refreshRate=") >= 0) { + minutesBetweenDataRefresh = line.substring(line.lastIndexOf("refreshRate=") + 12).toInt(); + Serial.println("minutesBetweenDataRefresh=" + String(minutesBetweenDataRefresh)); + } + if (line.indexOf("marqueeMessage=") >= 0) { + marqueeMessage = line.substring(line.lastIndexOf("marqueeMessage=") + 15); + marqueeMessage.trim(); + Serial.println("marqueeMessage=" + marqueeMessage); + } + if (line.indexOf("timeDisplayTurnsOn=") >= 0) { + timeDisplayTurnsOn = line.substring(line.lastIndexOf("timeDisplayTurnsOn=") + 19); + timeDisplayTurnsOn.trim(); + Serial.println("timeDisplayTurnsOn=" + timeDisplayTurnsOn); + } + if (line.indexOf("timeDisplayTurnsOff=") >= 0) { + timeDisplayTurnsOff = line.substring(line.lastIndexOf("timeDisplayTurnsOff=") + 20); + timeDisplayTurnsOff.trim(); + Serial.println("timeDisplayTurnsOff=" + timeDisplayTurnsOff); + } + if (line.indexOf("ledIntensity=") >= 0) { + displayIntensity = line.substring(line.lastIndexOf("ledIntensity=") + 13).toInt(); + Serial.println("displayIntensity=" + String(displayIntensity)); + } + if (line.indexOf("isOctoPrint=") >= 0) { + OCTOPRINT_ENABLED = line.substring(line.lastIndexOf("isOctoPrint=") + 12).toInt(); + Serial.println("OCTOPRINT_ENABLED=" + String(OCTOPRINT_ENABLED)); + } + if (line.indexOf("octoKey=") >= 0) { + OctoPrintApiKey = line.substring(line.lastIndexOf("octoKey=") + 8); + OctoPrintApiKey.trim(); + Serial.println("OctoPrintApiKey=" + OctoPrintApiKey); + } + if (line.indexOf("octoServer=") >= 0) { + OctoPrintServer = line.substring(line.lastIndexOf("octoServer=") + 11); + OctoPrintServer.trim(); + Serial.println("OctoPrintServer=" + OctoPrintServer); + } + if (line.indexOf("octoPort=") >= 0) { + OctoPrintPort = line.substring(line.lastIndexOf("octoPort=") + 9).toInt(); + Serial.println("OctoPrintPort=" + String(OctoPrintPort)); + } + if (line.indexOf("www_username=") >= 0) { + String temp = line.substring(line.lastIndexOf("www_username=") + 13); + temp.trim(); + temp.toCharArray(www_username, sizeof(temp)); + Serial.println("www_username=" + String(www_username)); + } + if (line.indexOf("www_password=") >= 0) { + String temp = line.substring(line.lastIndexOf("www_password=") + 13); + temp.trim(); + temp.toCharArray(www_password, sizeof(temp)); + Serial.println("www_password=" + String(www_password)); + } + if (line.indexOf("IS_BASIC_AUTH=") >= 0) { + IS_BASIC_AUTH = line.substring(line.lastIndexOf("IS_BASIC_AUTH=") + 14).toInt(); + Serial.println("IS_BASIC_AUTH=" + String(IS_BASIC_AUTH)); + } + } + fr.close(); + matrix.setIntensity(displayIntensity); + newsClient.updateNewsSource(NEWS_SOURCE); + weatherClient.setMetric(IS_METRIC); + weatherClient.updateCityIdList(CityIDs, 1); + printerClient.updateOctoPrintClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); + if (OCTOPRINT_ENABLED) { + printerClient.getPrinterJobResults(); + } +} + +void scrollMessage(String msg) { + msg += " "; // add a space at the end + for ( int i = 0 ; i < width * msg.length() + matrix.width() - 1 - spacer; i++ ) { + if (WEBSERVER_ENABLED) { + server.handleClient(); + } + if (ENABLE_OTA) { + ArduinoOTA.handle(); + } + if (refresh==1) i=0; + refresh=0; + matrix.fillScreen(LOW); + + int letter = i / width; + int x = (matrix.width() - 1) - i % width; + int y = (matrix.height() - 8) / 2; // center the text vertically + + while ( x + width - spacer >= 0 && letter >= 0 ) { + if ( letter < msg.length() ) { + matrix.drawChar(x, y, msg[letter], HIGH, LOW, 1); + } + + letter--; + x -= width; + } + + matrix.write(); // Send bitmap to display + delay(wait); + } + matrix.setCursor(0,0); +} + +void centerPrint(String msg) { + int x = (matrix.width() - (msg.length() * width)) / 2; + matrix.setCursor(x, 0); + matrix.print(msg); + matrix.write(); +} + +String decodeHtmlString(String msg) { + String decodedMsg = msg; + // Restore special characters that are misformed to %char by the client browser + decodedMsg.replace("+", " "); + decodedMsg.replace("%21", "!"); + decodedMsg.replace("%22", ""); + decodedMsg.replace("%23", "#"); + decodedMsg.replace("%24", "$"); + decodedMsg.replace("%25", "%"); + decodedMsg.replace("%26", "&"); + decodedMsg.replace("%27", "'"); + decodedMsg.replace("%28", "("); + decodedMsg.replace("%29", ")"); + decodedMsg.replace("%2A", "*"); + decodedMsg.replace("%2B", "+"); + decodedMsg.replace("%2C", ","); + decodedMsg.replace("%2F", "/"); + decodedMsg.replace("%3A", ":"); + decodedMsg.replace("%3B", ";"); + decodedMsg.replace("%3C", "<"); + decodedMsg.replace("%3D", "="); + decodedMsg.replace("%3E", ">"); + decodedMsg.replace("%3F", "?"); + decodedMsg.replace("%40", "@"); + decodedMsg.toUpperCase(); + decodedMsg.trim(); + return decodedMsg; }