diff --git a/README.md b/README.md index dec05d0..94f7183 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ SOFTWARE. * Configured through Web Interface * Display 3D print progress from your OctoPrint Server * Option to display Bitcoin current value +* Option to display Pi-hole status and graph (each pixel accross is 10 minutes) * Basic Authorization around Configuration web interface * Support for OTA (loading firmware over WiFi) * Update firmware through web interface diff --git a/marquee.ino.d1_mini_2.11.bin b/marquee.ino.d1_mini_2.11.bin deleted file mode 100644 index 9bbb005..0000000 Binary files a/marquee.ino.d1_mini_2.11.bin and /dev/null differ diff --git a/marquee.ino.d1_mini_2.12.bin b/marquee.ino.d1_mini_2.12.bin new file mode 100644 index 0000000..87cdccd Binary files /dev/null and b/marquee.ino.d1_mini_2.12.bin differ diff --git a/marquee.ino.d1_mini_wide_2.11.bin b/marquee.ino.d1_mini_wide_2.11.bin deleted file mode 100644 index cfa898d..0000000 Binary files a/marquee.ino.d1_mini_wide_2.11.bin and /dev/null differ diff --git a/marquee.ino.d1_mini_wide_2.12.bin b/marquee.ino.d1_mini_wide_2.12.bin new file mode 100644 index 0000000..25b3a5d Binary files /dev/null and b/marquee.ino.d1_mini_wide_2.12.bin differ diff --git a/marquee/PiHoleClient.cpp b/marquee/PiHoleClient.cpp new file mode 100644 index 0000000..d5cb355 --- /dev/null +++ b/marquee/PiHoleClient.cpp @@ -0,0 +1,300 @@ +/** 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. +*/ + +#include "PiHoleClient.h" + +PiHoleClient::PiHoleClient() { + //Constructor +} + +void PiHoleClient::getPiHoleData(String server, int port) { + + errorMessage = ""; + String response = ""; + + String apiGetData = "http://" + server + ":" + String(port) + "/admin/api.php?summary"; + Serial.println("Sending: " + apiGetData); + HTTPClient http; //Object of class HTTPClient + http.begin(apiGetData);// get the result + int httpCode = http.GET(); + //Check the returning code + if (httpCode > 0) { + response = http.getString(); + http.end(); //Close connection + if (httpCode != 200) { + // Bad Response Code + errorMessage = "Error response (" + String(httpCode) + "): " + response; + Serial.println(errorMessage); + return; + } + Serial.println("Response Code: " + String(httpCode)); + //Serial.println("Response: " + response); + } else { + errorMessage = "Failed to connect and get data: " + apiGetData; + Serial.println(errorMessage); + return; + } + + const size_t bufferSize = 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(17) + 470; + DynamicJsonBuffer jsonBuffer(bufferSize); + + // Parse JSON object + JsonObject& root = jsonBuffer.parseObject(response); + if (!root.success()) { + errorMessage = "Data Summary Parsing failed: " + apiGetData; + Serial.println(errorMessage); + return; + } + + piHoleData.domains_being_blocked = (const char*)root["domains_being_blocked"]; + piHoleData.dns_queries_today = (const char*)root["dns_queries_today"]; + piHoleData.ads_blocked_today = (const char*)root["ads_blocked_today"]; + piHoleData.ads_percentage_today = (const char*)root["ads_percentage_today"]; + piHoleData.unique_domains = (const char*)root["unique_domains"]; + piHoleData.queries_forwarded = (const char*)root["queries_forwarded"]; + piHoleData.queries_cached = (const char*)root["queries_cached"]; + piHoleData.clients_ever_seen = (const char*)root["clients_ever_seen"]; + piHoleData.unique_clients = (const char*)root["unique_clients"]; + piHoleData.dns_queries_all_types = (const char*)root["dns_queries_all_types"]; + piHoleData.reply_NODATA = (const char*)root["reply_NODATA"]; + piHoleData.reply_NXDOMAIN = (const char*)root["reply_NXDOMAIN"]; + piHoleData.reply_CNAME = (const char*)root["reply_CNAME"]; + piHoleData.reply_IP = (const char*)root["reply_IP"]; + piHoleData.privacy_level = (const char*)root["privacy_level"]; + piHoleData.piHoleStatus = (const char*)root["status"]; + + Serial.println("Pi-Hole Status: " + piHoleData.piHoleStatus); + Serial.println("Todays Percentage Blocked: " + piHoleData.ads_percentage_today); + Serial.println(); +} + +void PiHoleClient::getTopClientsBlocked(String server, int port, String apiKey) { + errorMessage = ""; + resetClientsBlocked(); + + if (apiKey == "") { + errorMessage = "Pi-hole API Key is required to view Top Clients Blocked."; + return; + } + + String response = ""; + + String apiGetData = "http://" + server + ":" + String(port) + "/admin/api.php?topClientsBlocked=3&auth=" + apiKey; + Serial.println("Sending: " + apiGetData); + HTTPClient http; //Object of class HTTPClient + http.begin(apiGetData);// get the result + int httpCode = http.GET(); + //Check the returning code + if (httpCode > 0) { + response = http.getString(); + http.end(); //Close connection + if (httpCode != 200) { + // Bad Response Code + errorMessage = "Error response (" + String(httpCode) + "): " + response; + Serial.println(errorMessage); + return; + } + Serial.println("Response Code: " + String(httpCode)); + //Serial.println("Response: " + response); + } else { + errorMessage = "Failed to get data: " + apiGetData; + Serial.println(errorMessage); + return; + } + + const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(3) + 70; + DynamicJsonBuffer jsonBuffer(bufferSize); + + // Parse JSON object + JsonObject& root = jsonBuffer.parseObject(response); + if (!root.success()) { + errorMessage = "Data Parsing failed -- verify your Pi-hole API key."; + Serial.println(errorMessage); + return; + } + + JsonObject& blocked = root["top_sources_blocked"]; + int count = 0; + for (JsonPair p : blocked) { + blockedClients[count].clientAddress = (const char*)p.key; + blockedClients[count].blockedCount = p.value.as(); + Serial.println("Blocked Client " + String(count+1) + ": " + blockedClients[count].clientAddress + " (" + String(blockedClients[count].blockedCount) + ")"); + count++; + } + Serial.println(); +} + +void PiHoleClient::getGraphData(String server, int port) { + + HTTPClient http; + + String apiGetData = "http://" + server + ":" + String(port) + "/admin/api.php?overTimeData10mins"; + resetBlockedGraphData(); + Serial.println("Getting Pi-Hole Graph Data"); + Serial.println(apiGetData); + http.begin(apiGetData); + int httpCode = http.GET(); + + String result = ""; + errorMessage = ""; + boolean track = false; + int countBracket = 0; + blockedCount = 0; + + if (httpCode > 0) { // checks for connection + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + if(httpCode == HTTP_CODE_OK) { + // get length of document (is -1 when Server sends no Content-Length header) + int len = http.getSize(); + // create buffer for read + char buff[128] = { 0 }; + // get tcp stream + WiFiClient * stream = http.getStreamPtr(); + // read all data from server + Serial.println("Start reading..."); + while(http.connected() && (len > 0 || len == -1)) { + // get available data size + size_t size = stream->available(); + if(size) { + // read up to 128 byte + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + for(int i=0;i= 3) { + if (buff[i] == ',' || buff[i] == '}') { + blocked[blockedCount] = result.toInt(); + if (blocked[blockedCount] > blockedHigh) { + blockedHigh = blocked[blockedCount]; + } + //Serial.println("Pi-hole Graph point (" + String(blockedCount+1) + "): " + String(blocked[blockedCount])); + blockedCount++; + result = ""; + track = false; + } else { + result += buff[i]; + } + } else if (buff[i] == '{') { + countBracket++; + } else if (countBracket >= 3 && buff[i] == ':') { + track = true; + } + } + + if(len > 0) + len -= c; + } + delay(1); + } + } + http.end(); + } else { + errorMessage = "Connection for Pi-Hole data failed: " + String(apiGetData); + Serial.println(errorMessage); //error message if no client connect + Serial.println(); + return; + } + + + Serial.println("High Value: " + String(blockedHigh)); + Serial.println("Count: " + String(blockedCount)); + Serial.println(); +} + +void PiHoleClient::resetClientsBlocked() { + for (int inx = 0; inx < 3; inx++) { + blockedClients[inx].clientAddress = ""; + blockedClients[inx].blockedCount = 0; + } +} + +void PiHoleClient::resetBlockedGraphData() { + for (int inx = 0; inx < 144; inx++) { + blocked[inx] = 0; + } + blockedCount = 0; + blockedHigh = 0; +} + +String PiHoleClient::getDomainsBeingBlocked() { + return piHoleData.domains_being_blocked; +} + +String PiHoleClient::getDnsQueriesToday() { + return piHoleData.dns_queries_today; +} + +String PiHoleClient::getAdsBlockedToday() { + return piHoleData.ads_blocked_today; +} + +String PiHoleClient::getAdsPercentageToday() { + return piHoleData.ads_percentage_today; +} + +String PiHoleClient::getUniqueClients() { + return piHoleData.unique_clients; +} + +String PiHoleClient::getClientsEverSeen() { + return piHoleData.clients_ever_seen; +} + +/* //Need to add the following + String getUniqueDomains(); + String getQueriesForwarded(); + String getQueriesCached(); + String getDnsQueriesAllTypes(); + String getReplyNODATA(); + String getReplyNXDOMAIN(); + String getReplyCNAME(); + String getReplyIP(); + String getPrivacyLevel(); + */ + + +String PiHoleClient::getPiHoleStatus() { + return piHoleData.piHoleStatus; +} + +String PiHoleClient::getError() { + return errorMessage; +} + +int *PiHoleClient::getBlockedAds() { + return blocked; +} + +int PiHoleClient::getBlockedCount() { + return blockedCount; +} + +int PiHoleClient::getBlockedHigh() { + return blockedHigh; +} + +String PiHoleClient::getTopClientBlocked(int index) { + return blockedClients[index].clientAddress; +} + +int PiHoleClient::getTopClientBlockedCount(int index) { + return blockedClients[index].blockedCount; +} diff --git a/marquee/PiHoleClient.h b/marquee/PiHoleClient.h new file mode 100644 index 0000000..be5bb0a --- /dev/null +++ b/marquee/PiHoleClient.h @@ -0,0 +1,105 @@ +/** The MIT License (MIT) + +Copyright (c) 2019 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. +*/ + +#pragma once +#include +#include +#include "libs/ArduinoJson/ArduinoJson.h" + +class PiHoleClient { + +private: + + WiFiClient getSubmitRequest(String apiGetData, String myServer, int myPort); + void resetClientsBlocked(); + void resetBlockedGraphData(); + + String errorMessage = ""; + + int blocked[144] = {0}; + int blockedCount = 0; + int blockedHigh = 0; + + typedef struct { + String clientAddress; + int blockedCount; + } ClientBlocked; + + ClientBlocked blockedClients[3]; + + typedef struct { + String domains_being_blocked; + String dns_queries_today; + String ads_blocked_today; + String ads_percentage_today; + String unique_domains; + String queries_forwarded; + String queries_cached; + String clients_ever_seen; + String unique_clients; + String dns_queries_all_types; + String reply_NODATA; + String reply_NXDOMAIN; + String reply_CNAME; + String reply_IP; + String privacy_level; + String piHoleStatus; + } phd; + + phd piHoleData; + + +public: + PiHoleClient(); + void getPiHoleData(String server, int port); + void getGraphData(String server, int port); + void getTopClientsBlocked(String server, int port, String apiKey); + + String getDomainsBeingBlocked(); + String getDnsQueriesToday(); + String getAdsBlockedToday(); + String getAdsPercentageToday(); + String getUniqueClients(); + String getClientsEverSeen(); + /* + String getUniqueDomains(); + String getQueriesForwarded(); + String getQueriesCached(); + String getDnsQueriesAllTypes(); + String getReplyNODATA(); + String getReplyNXDOMAIN(); + String getReplyCNAME(); + String getReplyIP(); + String getPrivacyLevel(); + + */ + String getPiHoleStatus(); + String getError(); + + int *getBlockedAds(); + int getBlockedCount(); + int getBlockedHigh(); + + String getTopClientBlocked(int index); + int getTopClientBlockedCount(int index); +}; diff --git a/marquee/Settings.h b/marquee/Settings.h index fce3ba3..fc88628 100644 --- a/marquee/Settings.h +++ b/marquee/Settings.h @@ -51,6 +51,7 @@ SOFTWARE. #include "NewsApiClient.h" #include "OctoPrintClient.h" #include "BitcoinApiClient.h" +#include "PiHoleClient.h" //****************************** // Start Settings @@ -101,16 +102,21 @@ String timeDisplayTurnsOff = "23:00"; // 24 Hour Format HH:MM -- Leave blank for boolean OCTOPRINT_ENABLED = false; boolean OCTOPRINT_PROGRESS = true; String OctoPrintApiKey = ""; // ApiKey from your User Account on OctoPrint -String OctoPrintServer = ""; // IP or Address of your OctoPrint Server (DO NOT include http://) -int OctoPrintPort = 80; // the port you are running your OctoPrint server on (usually 80); -String OctoAuthUser = ""; // only used if you have haproxy or basic athentintication turned on (not default) -String OctoAuthPass = ""; // only used with haproxy or basic auth (only needed if you must authenticate) +String OctoPrintServer = ""; // IP or Address of your OctoPrint Server (DO NOT include http://) +int OctoPrintPort = 80; // the port you are running your OctoPrint server on (usually 80); +String OctoAuthUser = ""; // only used if you have haproxy or basic athentintication turned on (not default) +String OctoAuthPass = ""; // only used with haproxy or basic auth (only needed if you must authenticate) // Bitcoin Client - NONE or empty is off String BitcoinCurrencyCode = "NONE"; // Change to USD, GBD, EUR, or NONE -- this can be managed in the Web Interface -boolean ENABLE_OTA = true; // this will allow you to load firmware to the device over WiFi (see OTA for ESP8266) -String OTA_Password = ""; // Set an OTA password here -- leave blank if you don't want to be prompted for password +// Pi-hole Client -- monitor basic stats from your Pi-hole server (see http://pi-hole.net) +boolean USE_PIHOLE = false; // Set true to display your Pi-hole details +String PiHoleServer = ""; // IP or Address only (DO NOT include http://) +int PiHolePort = 80; // Port of your Pi-hole address (default 80) + +boolean ENABLE_OTA = true; // this will allow you to load firmware to the device over WiFi (see OTA for ESP8266) +String OTA_Password = ""; // Set an OTA password here -- leave blank if you don't want to be prompted for password //****************************** // End Settings diff --git a/marquee/marquee.ino b/marquee/marquee.ino index d62d14d..ce9d5df 100644 --- a/marquee/marquee.ino +++ b/marquee/marquee.ino @@ -27,7 +27,7 @@ #include "Settings.h" -#define VERSION "2.11" +#define VERSION "2.12" #define HOSTNAME "CLOCK-" #define CONFIG "/conf.txt" @@ -84,18 +84,36 @@ boolean SHOW_WIND = true; OctoPrintClient printerClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort, OctoAuthUser, OctoAuthPass); int printerCount = 0; +// Pi-hole Client +PiHoleClient piholeClient; + // Bitcoin Client BitcoinApiClient bitcoinClient; ESP8266WebServer server(WEBSERVER_PORT); ESP8266HTTPUpdateServer serverUpdater; -String CHANGE_FORM1 = "

Configure:

" +static const char WEB_ACTIONS1[] PROGMEM = " Home" + " Configure" + " News" + " OctoPrint"; + +static const char WEB_ACTIONS2[] PROGMEM = " Bitcoin" + " Pi-hole" + " Refresh Data" + ""; + +static const char WEB_ACTION3[] PROGMEM = " Reset Settings" + " Forget WiFi" + " Firmware Update" + " About"; + +static const char CHANGE_FORM1[] PROGMEM = "

Configure:

" "" "" "" "" - "

" + "

" "

" "

Use Metric (Celsius)

" "

Display Date

" @@ -105,7 +123,7 @@ String CHANGE_FORM1 = " Display Wind

" "

Use 24 Hour Clock (military time)

"; -String CHANGE_FORM2 = "

Show PM indicator (only 12h format)

" +static const char CHANGE_FORM2[] PROGMEM = "

Show PM indicator (only 12h format)

" "

Flash : in the time

" "

" "

" @@ -115,13 +133,17 @@ String CHANGE_FORM2 = "

Minutes Between Refresh Data

" "

Minutes Between Scrolling Data

"; -String CHANGE_FORM3 = "

Use Security Credentials for Configuration Changes

" +static const char CHANGE_FORM3[] PROGMEM = "

Use Security Credentials for Configuration Changes

" "

" "

" "

" ""; -String CURRENCY_OPTIONS = "" +static const char BITCOIN_FORM[] PROGMEM = "

Bitcoin Configuration:

" + "

Select Bitcoin Currency

" + "
"; + +static const char CURRENCY_OPTIONS[] PROGMEM = "" "" "" "" @@ -132,6 +154,47 @@ String CURRENCY_OPTIONS = "" "" ""; +static const char WIDECLOCK_FORM[] PROGMEM = "

Wide Clock Configuration:

" + "

Wide Clock Display Format

" + "
"; + +static const char PIHOLE_FORM[] PROGMEM = "

Pi-hole Configuration:

" + "

Show Pi-hole Statistics

" + "" + "" + "

" + "
" + ""; + +static const char PIHOLE_TEST[] PROGMEM = ""; + +static const char NEWS_FORM1[] PROGMEM = "

News Configuration:

" + "

Display News Headlines

" + "" + "" + "

Select News Source

" + "" + "
"; + +static const char OCTO_FORM[] PROGMEM = "

OctoPrint Configuration:

" + "

Show OctoPrint Status

" + "

Show OctoPrint progress with clock

" + "" + "" + "" + "" + "" + "
" + ""; + + + const int TIMEOUT = 500; // 500 = 1/2 second int timeoutCount = 0; @@ -244,6 +307,7 @@ void setup() { server.on("/savewideclock", handleSaveWideClock); server.on("/savenews", handleSaveNews); server.on("/saveoctoprint", handleSaveOctoprint); + server.on("/savepihole", handleSavePihole); server.on("/systemreset", handleSystemReset); server.on("/forgetwifi", handleForgetWifi); server.on("/configure", handleConfigure); @@ -251,6 +315,7 @@ void setup() { server.on("/configurewideclock", handleWideClockConfigure); server.on("/configurenews", handleNewsConfigure); server.on("/configureoctoprint", handleOctoprintConfigure); + server.on("/configurepihole", handlePiholeConfigure); server.on("/display", handleDisplay); server.onNotFound(redirectHome); serverUpdater.setup(&server, "/update", www_username, www_password); @@ -346,8 +411,16 @@ void loop() { if (BitcoinCurrencyCode != "NONE" && BitcoinCurrencyCode != "") { msg += " Bitcoin: " + bitcoinClient.getRate() + " " + bitcoinClient.getCode() + " "; } + if (USE_PIHOLE) { + piholeClient.getPiHoleData(PiHoleServer, PiHolePort); + piholeClient.getGraphData(PiHoleServer, PiHolePort); + if (piholeClient.getPiHoleStatus() != "") { + msg += " Pi-hole (" + piholeClient.getPiHoleStatus() + "): " + piholeClient.getAdsPercentageToday() + "% "; + } + } scrollMessage(msg); + drawPiholeGraph(); } } @@ -464,6 +537,21 @@ void handleSaveOctoprint() { redirectHome(); } +void handleSavePihole() { + if (!athentication()) { + return server.requestAuthentication(); + } + USE_PIHOLE = server.hasArg("displaypihole"); + PiHoleServer = server.arg("piholeAddress"); + PiHolePort = server.arg("piholePort").toInt(); + writeCityIds(); + if (USE_PIHOLE) { + piholeClient.getPiHoleData(PiHoleServer, PiHolePort); + piholeClient.getGraphData(PiHoleServer, PiHolePort); + } + redirectHome(); +} + void handleLocations() { if (!athentication()) { return server.requestAuthentication(); @@ -537,12 +625,8 @@ void handleBitcoinConfigure() { sendHeader(); - String BITCOIN_FORM = "

Bitcoin Configuration:

" - "

Select Bitcoin Currency

" - "
"; - - String form = BITCOIN_FORM; - String bitcoinOptions = CURRENCY_OPTIONS; + String form = (const char*)BITCOIN_FORM; + String bitcoinOptions = (const char*)CURRENCY_OPTIONS; bitcoinOptions.replace(BitcoinCurrencyCode + "'", BitcoinCurrencyCode + "' selected"); form.replace("%BITCOINOPTIONS%", bitcoinOptions); server.sendContent(form); //Send another Chunk of form @@ -560,10 +644,6 @@ void handleWideClockConfigure() { } digitalWrite(externalLight, LOW); - String WIDECLOCK_FORM = "

Wide Clock Configuration:

" - "

Wide Clock Display Format

" - "
"; - server.sendHeader("Cache-Control", "no-cache, no-store"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); @@ -574,7 +654,7 @@ void handleWideClockConfigure() { if (numberOfHorizontalDisplays >= 8) { // Wide display options - String form = WIDECLOCK_FORM; + String form = (const char*)WIDECLOCK_FORM; String clockOptions = ""; clockOptions.replace(Wide_Clock_Style + "'", Wide_Clock_Style + "' selected"); form.replace("%WIDECLOCKOPTIONS%", clockOptions); @@ -593,19 +673,7 @@ void handleNewsConfigure() { return server.requestAuthentication(); } digitalWrite(externalLight, LOW); - - String NEWS_FORM1 = "

News Configuration:

" - "

Display News Headlines

" - "" - "" - "

Select News Source

" - "" - "
"; - server.sendHeader("Cache-Control", "no-cache, no-store"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); @@ -614,7 +682,7 @@ void handleNewsConfigure() { sendHeader(); - String form = NEWS_FORM1; + String form = (const char*)NEWS_FORM1; String isNewsDisplayedChecked = ""; if (NEWS_ENABLED) { isNewsDisplayedChecked = "checked='checked'"; @@ -637,17 +705,6 @@ void handleOctoprintConfigure() { } digitalWrite(externalLight, LOW); - String OCTO_FORM = "

OctoPrint Configuration:

" - "

Show OctoPrint Status

" - "

Show OctoPrint progress with clock

" - "" - "" - "" - "" - "" - "
" - ""; - server.sendHeader("Cache-Control", "no-cache, no-store"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); @@ -656,7 +713,7 @@ void handleOctoprintConfigure() { sendHeader(); - String form = OCTO_FORM; + String form = (const char*)OCTO_FORM; String isOctoPrintDisplayedChecked = ""; if (OCTOPRINT_ENABLED) { isOctoPrintDisplayedChecked = "checked='checked'"; @@ -681,6 +738,42 @@ void handleOctoprintConfigure() { digitalWrite(externalLight, HIGH); } +void handlePiholeConfigure() { + if (!athentication()) { + return server.requestAuthentication(); + } + 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", ""); + + sendHeader(); + + String form = (const char*)PIHOLE_TEST; + server.sendContent(form); + + form = (const char*)PIHOLE_FORM; + String isPiholeDisplayedChecked = ""; + if (USE_PIHOLE) { + isPiholeDisplayedChecked = "checked='checked'"; + } + form.replace("%PIHOLECHECKED%", isPiholeDisplayedChecked); + form.replace("%PIHOLEADDRESS%", PiHoleServer); + form.replace("%PIHOLEPORT%", String(PiHolePort)); + + server.sendContent(form); + form = ""; + + sendFooter(); + + server.sendContent(""); + server.client().stop(); + digitalWrite(externalLight, HIGH); +} + void handleConfigure() { if (!athentication()) { return server.requestAuthentication(); @@ -696,7 +789,7 @@ void handleConfigure() { sendHeader(); - String form = CHANGE_FORM1; + String form = (const char*)CHANGE_FORM1; form.replace("%TIMEDBKEY%", TIMEDBKEY); form.replace("%WEATHERKEY%", APIKEY); @@ -744,7 +837,7 @@ void handleConfigure() { form.replace("%CHECKED%", checked); server.sendContent(form); - form = CHANGE_FORM2; + form = (const char*)CHANGE_FORM2; String isPmChecked = ""; if (IS_PM) { isPmChecked = "checked='checked'"; @@ -760,7 +853,7 @@ void handleConfigure() { form.replace("%ENDTIME%", timeDisplayTurnsOff); form.replace("%INTENSITYOPTIONS%", String(displayIntensity)); String dSpeed = String(displayScrollSpeed); - String scrollOptions = ""; + String scrollOptions = ""; scrollOptions.replace(dSpeed + "'", dSpeed + "' selected" ); form.replace("%SCROLLOPTIONS%", scrollOptions); String minutes = String(minutesBetweenDataRefresh); @@ -771,7 +864,7 @@ void handleConfigure() { server.sendContent(form); //Send another chunk of the form - form = CHANGE_FORM3; + form = (const char*)CHANGE_FORM3; String isUseSecurityChecked = ""; if (IS_BASIC_AUTH) { isUseSecurityChecked = "checked='checked'"; @@ -893,32 +986,18 @@ void redirectHome() { } void sendHeader() { - String WEB_ACTIONS1 = " Home" - " Configure" - " News" - " OctoPrint"; - - String WEB_ACTIONS2 = " Bitcoin" - " Refresh Data" - ""; - - String WEB_ACTION3 = " Reset Settings" - " Forget WiFi" - " Firmware Update" - " About"; - - String menu = WEB_ACTIONS1; + String menu = (const char*)WEB_ACTIONS1; if (numberOfHorizontalDisplays >= 8) { - menu += " Wide Clock"; + menu += " Wide Clock"; } - menu += WEB_ACTIONS2; + menu += (const char*)WEB_ACTIONS2; if (displayOn) { - menu += " Turn Display OFF"; + menu += " Turn Display OFF"; } else { - menu += " Turn Display ON"; + menu += " Turn Display ON"; } - menu += WEB_ACTION3; + menu += (const char*)WEB_ACTION3; String html = ""; html += "Marquee Scroller"; @@ -926,18 +1005,18 @@ void sendHeader() { html += ""; html += ""; html += ""; - html += ""; + html += ""; html += ""; server.sendContent(html); html = ""; - html += "

Weather Marquee

"; + html += "

Weather Marquee

"; html += ""; @@ -953,9 +1032,9 @@ void sendFooter() { String html = "


"; html += ""; html += "
"; - html += " Version: " + String(VERSION) + "
"; - html += " Next Update: " + getTimeTillUpdate() + "
"; - html += " Signal Strength: "; + html += " Version: " + String(VERSION) + "
"; + html += " Next Update: " + getTimeTillUpdate() + "
"; + html += " Signal Strength: "; html += String(rssi) + "%"; html += "
"; html += ""; @@ -1007,7 +1086,7 @@ void displayWeatherData() { html += weatherClient.getCondition(0) + " (" + weatherClient.getDescription(0) + ")
"; html += temperature + " " + getTempSymbol() + "
"; html += time + "
"; - html += " Map It!
"; + html += " Map It!
"; html += "


"; } @@ -1038,6 +1117,25 @@ void displayWeatherData() { html = ""; } + if (USE_PIHOLE) { + if (piholeClient.getError() == "") { + html = "
Pi-hole
" + "Total Queries (" + piholeClient.getUniqueClients() + " clients): " + piholeClient.getDnsQueriesToday() + "
" + "Queries Blocked: " + piholeClient.getAdsBlockedToday() + "
" + "Percent Blocked: " + piholeClient.getAdsPercentageToday() + "%
" + "Domains on Blocklist: " + piholeClient.getDomainsBeingBlocked() + "
" + "Status: " + piholeClient.getPiHoleStatus() + "
" + "


"; + } else { + html = "
Pi-hole Error"; + html += "Please Configure for Pi-hole
"; + html += "Status: Error Getting Data
"; + html += "Reason: " + piholeClient.getError() + "


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

News (" + NEWS_SOURCE + ")

"; if (newsClient.getTitle(0) == "") { @@ -1222,6 +1320,9 @@ String writeCityIds() { f.println("SHOW_HUMIDITY=" + String(SHOW_HUMIDITY)); f.println("SHOW_WIND=" + String(SHOW_WIND)); f.println("SHOW_DATE=" + String(SHOW_DATE)); + f.println("USE_PIHOLE=" + String(USE_PIHOLE)); + f.println("PiHoleServer=" + PiHoleServer); + f.println("PiHolePort=" + String(PiHolePort)); } f.close(); readCityIds(); @@ -1397,6 +1498,19 @@ void readCityIds() { SHOW_DATE = line.substring(line.lastIndexOf("SHOW_DATE=") + 10).toInt(); Serial.println("SHOW_DATE=" + String(SHOW_DATE)); } + if (line.indexOf("USE_PIHOLE=") >= 0) { + USE_PIHOLE = line.substring(line.lastIndexOf("USE_PIHOLE=") + 11).toInt(); + Serial.println("USE_PIHOLE=" + String(USE_PIHOLE)); + } + if (line.indexOf("PiHoleServer=") >= 0) { + PiHoleServer = line.substring(line.lastIndexOf("PiHoleServer=") + 13); + PiHoleServer.trim(); + Serial.println("PiHoleServer=" + PiHoleServer); + } + if (line.indexOf("PiHolePort=") >= 0) { + PiHolePort = line.substring(line.lastIndexOf("PiHolePort=") + 11).toInt(); + Serial.println("PiHolePort=" + String(PiHolePort)); + } } fr.close(); matrix.setIntensity(displayIntensity); @@ -1439,6 +1553,44 @@ void scrollMessage(String msg) { matrix.setCursor(0, 0); } +void drawPiholeGraph() { + if (!USE_PIHOLE || piholeClient.getBlockedCount() == 0) { + return; + } + int count = piholeClient.getBlockedCount(); + int high = 0; + int row = matrix.width() - 1; + int yval = 0; + + // get the high value for the sample that will be on the screen + for (int inx = count; inx >= (count - matrix.width()); inx--) { + if (piholeClient.getBlockedAds()[inx] > high) { + high = piholeClient.getBlockedAds()[inx]; + } + } + + for (int inx = (count-1); inx >= (count - matrix.width()); inx--) { + yval = map(piholeClient.getBlockedAds()[inx], high, 0, 0, 7); + //Serial.println("Value: " + String(piholeClient.getBlockedAds()[inx])); + //Serial.println("x: " + String(row) + " y:" + String(yval) + " h:" + String(8-yval)); + matrix.drawFastVLine(row, yval, 8-yval, HIGH); + if (row == 0) { + break; + } + row--; + } + matrix.write(); + for (int wait = 0; wait < 500; wait++) { + if (WEBSERVER_ENABLED) { + server.handleClient(); + } + if (ENABLE_OTA) { + ArduinoOTA.handle(); + } + delay(20); + } +} + void centerPrint(String msg) { centerPrint(msg, false); }