diff --git a/printermonitor/NewsApiClient.cpp b/printermonitor/NewsApiClient.cpp
new file mode 100644
index 0000000..72a069c
--- /dev/null
+++ b/printermonitor/NewsApiClient.cpp
@@ -0,0 +1,184 @@
+/** 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 "NewsApiClient.h"
+
+
+
+#define arr_len( x ) ( sizeof( x ) / sizeof( *x ) )
+
+NewsApiClient::NewsApiClient(String ApiKey, String NewsSource) {
+ updateNewsClient(ApiKey, NewsSource);
+}
+
+void NewsApiClient::updateNewsClient(String ApiKey, String NewsSource) {
+ mySource = NewsSource;
+ myApiKey = ApiKey;
+}
+
+void NewsApiClient::updateNews() {
+ JsonStreamingParser parser;
+ parser.setListener(this);
+ HTTPClient http;
+
+ String apiGetData = "http://" + String(servername) + "/v2/top-headlines?sources=" + mySource + "&apiKey=" + myApiKey;
+ //String apiGetData = "http://" + String(servername) + "/v2/top-headlines?country=nl&apiKey=" + myApiKey;
+
+ Serial.println("Getting News Data");
+ Serial.println(apiGetData);
+ http.begin(apiGetData);
+ int httpCode = http.GET();
+
+ if (httpCode > 0) { // checks for connection
+ Serial.printf("[HTTP] GET... code: %d\n", httpCode);
+ if(httpCode == HTTP_CODE_OK) {
+ // get lenght 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 parsing...");
+ 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 0)
+ len -= c;
+ }
+ delay(1);
+ }
+ }
+ http.end();
+ } else {
+ Serial.println("connection for news data failed: " + String(apiGetData)); //error message if no client connect
+ Serial.println();
+ return;
+ }
+}
+
+String NewsApiClient::getTitle(int index) {
+ return news[index].title;
+}
+
+String NewsApiClient::getDescription(int index) {
+ return news[index].description;
+}
+
+String NewsApiClient::getUrl(int index) {
+ return news[index].url;
+}
+
+void NewsApiClient::updateNewsSource(String source) {
+ mySource = source;
+}
+
+void NewsApiClient::whitespace(char c) {
+
+}
+
+void NewsApiClient::startDocument() {
+ counterTitle = 0;
+}
+
+void NewsApiClient::key(String key) {
+ currentKey = key;
+}
+
+void NewsApiClient::value(String value) {
+ if (counterTitle == 10) {
+ // we are full so return
+ return;
+ }
+ if (currentKey == "title") {
+ news[counterTitle].title = cleanText(value);
+ }
+ if (currentKey == "description") {
+ news[counterTitle].description = cleanText(value);
+ }
+ if (currentKey == "url") {
+ news[counterTitle].url = value;
+ counterTitle++;
+ }
+ Serial.println(currentKey + "=" + value);
+}
+
+void NewsApiClient::endArray() {
+}
+
+void NewsApiClient::endObject() {
+}
+void NewsApiClient::startArray() {
+}
+
+void NewsApiClient::startObject() {
+}
+
+void NewsApiClient::endDocument() {
+}
+
+String NewsApiClient::cleanText(String text) {
+ text.replace("’", "'");
+ text.replace("“", "\"");
+ text.replace("”", "\"");
+ text.replace("`", "'");
+ text.replace("‘", "'");
+ text.replace("\\\"", "'");
+ text.replace("•", "-");
+ text.replace("é", "e");
+ text.replace("è", "e");
+ text.replace("ë", "e");
+ text.replace("ê", "e");
+ text.replace("à", "a");
+ text.replace("â", "a");
+ text.replace("ù", "u");
+ text.replace("ç", "c");
+ text.replace("î", "i");
+ text.replace("ï", "i");
+ text.replace("ô", "o");
+ text.replace("…", "...");
+ text.replace("–", "-");
+ text.replace("Â", "A");
+ text.replace("À", "A");
+ text.replace("æ", "ae");
+ text.replace("Æ", "AE");
+ text.replace("É", "E");
+ text.replace("È", "E");
+ text.replace("Ë", "E");
+ text.replace("Ô", "O");
+ text.replace("Ö", "O");
+ text.replace("œ", "oe");
+ text.replace("Œ", "OE");
+ text.replace("Ù", "U");
+ text.replace("Û", "U");
+ text.replace("Ü", "U");
+ return text;
+}
diff --git a/printermonitor/NewsApiClient.h b/printermonitor/NewsApiClient.h
new file mode 100644
index 0000000..e99e589
--- /dev/null
+++ b/printermonitor/NewsApiClient.h
@@ -0,0 +1,70 @@
+/** 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.
+*/
+
+#pragma once
+#include
+#include
+#include
+#include
+
+class NewsApiClient: public JsonListener {
+
+ private:
+ String mySource = "";
+ String myApiKey = "";
+
+ String currentKey = "";
+ int counterTitle = 0;
+
+ typedef struct {
+ String title;
+ String description;
+ String url;
+ } newsfeed;
+
+ newsfeed news[10];
+
+ const char* servername = "newsapi.org"; // remote server we will connect to
+
+ public:
+ NewsApiClient(String ApiKey, String NewsSource);
+ void updateNewsClient(String ApiKey, String NewsSource);
+ void updateNews();
+ void updateNewsSource(String source);
+
+ String getTitle(int index);
+ String getDescription(int index);
+ String getUrl(int index);
+ String cleanText(String text);
+
+ virtual void whitespace(char c);
+ virtual void startDocument();
+ virtual void key(String key);
+ virtual void value(String value);
+ virtual void endArray();
+ virtual void endObject();
+ virtual void endDocument();
+ virtual void startArray();
+ virtual void startObject();
+
+};
diff --git a/printermonitor/Settings.h b/printermonitor/Settings.h
index 5cd795c..2980709 100644
--- a/printermonitor/Settings.h
+++ b/printermonitor/Settings.h
@@ -48,6 +48,7 @@ SOFTWARE.
#include "SH1106Wire.h"
#include "SSD1306Wire.h"
#include "OLEDDisplayUi.h"
+#include "NewsApiClient.h"
//******************************
// Start Settings
@@ -77,6 +78,10 @@ boolean IS_24HOUR = false; // 23:00 millitary 24 hour clock
int minutesBetweenDataRefresh = 15;
boolean DISPLAYCLOCK = true; // true = Show Clock when not printing / false = turn off display when not printing
+boolean NEWS_ENABLED = true;
+String NEWS_API_KEY = ""; // Get your News API Key from https://newsapi.org
+String NEWS_SOURCE = "rtl-nieuws"; // https://newsapi.org/sources to get full list of news sources available
+
// Display Settings
const int I2C_DISPLAY_ADDRESS = 0x3c; // I2C Address of your Display (usually 0x3c or 0x3d)
const int SDA_PIN = D2;
@@ -90,4 +95,4 @@ String OTA_Password = ""; // Set an OTA password here -- leave blank if you
// End Settings
//******************************
-String themeColor = "light-green"; // this can be changed later in the web interface.
+String themeColor = "light-green"; // this can be changed later in the web interface.
diff --git a/printermonitor/printermonitor.ino b/printermonitor/printermonitor.ino
index 9914324..f8e4916 100644
--- a/printermonitor/printermonitor.ino
+++ b/printermonitor/printermonitor.ino
@@ -66,7 +66,7 @@ void drawClockHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
// Set the number of Frames supported
const int numberOfFrames = 3;
FrameCallback frames[numberOfFrames];
-FrameCallback clockFrame[2];
+FrameCallback clockFrame[20];
boolean isClockOn = false;
OverlayCallback overlays[] = { drawHeaderOverlay };
@@ -90,6 +90,32 @@ int printerCount = 0;
// Weather Client
OpenWeatherMapClient weatherClient(WeatherApiKey, CityIDs, 1, IS_METRIC);
+// News Client
+NewsApiClient newsClient(NEWS_API_KEY, NEWS_SOURCE);
+int newsIndex = 0;
+
+String NEWS_OPTIONS = ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
+
//declairing prototypes
void configModeCallback (WiFiManager *myWiFiManager);
int8_t getWifiQuality();
@@ -99,6 +125,7 @@ ESP8266WebServer server(WEBSERVER_PORT);
String WEB_ACTIONS = " Home"
" Configure"
" Weather"
+ " News"
" Reset Settings"
" Forget WiFi"
" About";
@@ -260,6 +287,8 @@ void setup() {
server.on("/updateweatherconfig", handleUpdateWeather);
server.on("/configure", handleConfigure);
server.on("/configureweather", handleWeatherConfigure);
+ server.on("/updatenewsconfig", handleUpdateNews);
+ server.on("/configurenews", handleNewsConfigure);
server.onNotFound(redirectHome);
// Start the server
server.begin();
@@ -364,6 +393,11 @@ void getUpdateTime() {
weatherClient.updateWeather();
}
+ if (displayOn && NEWS_ENABLED) {
+ Serial.println("Getting News Data for " + NEWS_SOURCE);
+ newsClient.updateNews();
+ }
+
Serial.println("Updating Time...");
//Update the Time
timeClient.updateTime();
@@ -478,6 +512,53 @@ void handleWeatherConfigure() {
digitalWrite(externalLight, HIGH);
}
+void handleNewsConfigure() {
+ if (!server.authenticate(www_username, www_password)) {
+ return server.requestAuthentication();
+ }
+ digitalWrite(externalLight, LOW);
+ String html = "";
+
+ String NEWS_FORM1 = "";
+
+ 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 = NEWS_FORM1;
+ String isNewsDisplayedChecked = "";
+ if (NEWS_ENABLED) {
+ isNewsDisplayedChecked = "checked='checked'";
+ }
+ form.replace("%NEWSCHECKED%", isNewsDisplayedChecked);
+ form.replace("%NEWSKEY%", NEWS_API_KEY);
+ server.sendContent(form); //Send first Chunk of form
+ String newsOptions = NEWS_OPTIONS;
+ newsOptions.replace(">" + NEWS_SOURCE + "<", " selected>" + NEWS_SOURCE + "<");
+ server.sendContent(newsOptions);
+ server.sendContent(NEWS_FORM2);
+
+ html = getFooter();
+ server.sendContent(html);
+
+ server.sendContent("");
+ server.client().stop();
+ digitalWrite(externalLight, HIGH);
+}
+
+
void handleConfigure() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
@@ -531,6 +612,28 @@ void handleConfigure() {
digitalWrite(externalLight, HIGH);
}
+
+void handleUpdateNews() {
+ if (!server.authenticate(www_username, www_password)) {
+ return server.requestAuthentication();
+ }
+ NEWS_ENABLED = server.hasArg("displaynews");
+ NEWS_API_KEY = server.arg("newsApiKey");
+ NEWS_SOURCE = server.arg("newssource");
+
+ writeSettings();
+ isClockOn = false; // this will force a check for the display
+ checkDisplay();
+
+ if (NEWS_ENABLED)
+ {
+ newsClient.updateNews();
+ }
+ redirectHome();
+}
+
+
+
void displayMessage(String message) {
digitalWrite(externalLight, LOW);
@@ -695,6 +798,25 @@ void displayPrinterStatus() {
html += "
";
}
+ if (NEWS_ENABLED) {
+ if (NEWS_API_KEY == "" || NEWS_SOURCE == "") {
+ html += "Please Configure News API and News Source
";
+ } else {
+ if (newsClient.getTitle(0) == "") {
+ html += "Error: No news headlines found
";
+ } else {
+ html += "" + NEWS_SOURCE + "
";
+ html += "
";
+ html += "
";
+ html += "
";
+ }
+ }
+ }
server.sendContent(html); // spit out what we got
html = ""; // fresh start
}
@@ -805,10 +927,90 @@ void drawWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int
display->setFont(ArialMT_Plain_16);
display->drawString(0 + x, 24 + y, weatherClient.getCondition(0));
- display->setFont((const uint8_t*)Meteocons_Plain_42);
+ display->setFont(Meteocons_Plain_42);
display->drawString(86 + x, 0 + y, weatherClient.getWeatherIcon(0));
}
+void drawNews(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y, int newsIndex)
+{
+ display->setFont(ArialMT_Plain_10);
+ display->setTextAlignment(TEXT_ALIGN_CENTER);
+ //display->drawStringMaxWidth(64 + x, 0, 128, newsClient.getTitle(newsIndex));
+
+ // Based on the drawStringMaxWidth function of the SSD1306Wire module
+ char text[100];
+ newsClient.getTitle(newsIndex).toCharArray(text, 99);
+ uint16_t length = strlen(text);
+ uint16_t lastDrawnPos = 0;
+ uint16_t lineNumber = 0;
+ uint16_t strWidth = 0;
+ uint16_t preferredBreakpoint = 0;
+ uint16_t widthAtBreakpoint = 0;
+
+ for (uint16_t i = 0; i < length && lineNumber < 4 ; i++) {
+ strWidth += display->getStringWidth(&text[i], 1);
+
+ // Always try to break on a space or dash
+ if (text[i] == ' ' || text[i]== '-') {
+ preferredBreakpoint = i;
+ widthAtBreakpoint = strWidth;
+ }
+
+ if (strWidth >= 128) {
+ if (preferredBreakpoint == 0) {
+ preferredBreakpoint = i;
+ widthAtBreakpoint = strWidth;
+ }
+ text[preferredBreakpoint] = '\0';
+ display->drawString(64 + x, y + ((lineNumber++) * 11) , &text[lastDrawnPos]);
+ lastDrawnPos = preferredBreakpoint + 1;
+ // It is possible that we did not draw all letters to i so we need
+ // to account for the width of the chars from `i - preferredBreakpoint`
+ // by calculating the width we did not draw yet.
+ strWidth = strWidth - widthAtBreakpoint;
+ preferredBreakpoint = 0;
+ }
+ }
+
+ // Draw last part if needed
+ if (lastDrawnPos < length && lineNumber < 4) {
+ display->drawString(64 + x, y + (lineNumber * 11) , &text[lastDrawnPos]);
+ }
+}
+
+// It look silly to have then function which do the same but i did not find a way to pass the news index
+// trough the frame function
+void drawNewsPage1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 0);
+}
+void drawNewsPage2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 1);
+}
+void drawNewsPage3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 2);
+}
+void drawNewsPage4(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 3);
+}
+void drawNewsPage5(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 4);
+}
+void drawNewsPage6(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 5);
+}
+void drawNewsPage7(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 6);
+}
+void drawNewsPage8(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 7);
+}
+void drawNewsPage9(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 8);
+}
+void drawNewsPage10(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
+ drawNews(display, state, x, y, 9);
+}
+
String getTempSymbol() {
return getTempSymbol(false);
}
@@ -941,6 +1143,9 @@ void writeSettings() {
f.println("weatherKey=" + WeatherApiKey);
f.println("CityID=" + String(CityIDs[0]));
f.println("isMetric=" + String(IS_METRIC));
+ f.println("displaynews=" + String(NEWS_ENABLED));
+ f.println("newsApiKey=" + String(NEWS_API_KEY));
+ f.println("newssource=" + String(NEWS_SOURCE));
}
f.close();
readSettings();
@@ -1037,6 +1242,20 @@ void readSettings() {
IS_METRIC = line.substring(line.lastIndexOf("isMetric=") + 9).toInt();
Serial.println("IS_METRIC=" + String(IS_METRIC));
}
+ if (line.indexOf("displaynews=") >= 0) {
+ NEWS_ENABLED = line.substring(line.lastIndexOf("displaynews=") + 12).toInt();
+ Serial.println("NEWS_ENABLED=" + String(NEWS_ENABLED));
+ }
+ if (line.indexOf("newsApiKey=") >= 0) {
+ NEWS_API_KEY = line.substring(line.lastIndexOf("newsApiKey=") + 11);
+ NEWS_API_KEY.trim();
+ Serial.println("NEWS_API_KEY=" + NEWS_API_KEY);
+ }
+ if (line.indexOf("newssource=") >= 0) {
+ NEWS_SOURCE = line.substring(line.lastIndexOf("newssource=") + 11);
+ NEWS_SOURCE.trim();
+ Serial.println("NEWS_API_KEY=" + NEWS_SOURCE);
+ }
}
fr.close();
printerClient.updateOctoPrintClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort, OctoAuthUser, OctoAuthPass);
@@ -1044,6 +1263,7 @@ void readSettings() {
weatherClient.setMetric(IS_METRIC);
weatherClient.updateCityIdList(CityIDs, 1);
timeClient.setUtcOffset(UtcOffset);
+ newsClient.updateNewsClient(NEWS_API_KEY, NEWS_SOURCE);
}
int getMinutesFromLastRefresh() {
@@ -1096,11 +1316,34 @@ void checkDisplay() {
ui.disableAutoTransition();
ui.setFrames(clockFrame, 1);
clockFrame[0] = drawClock;
- } else {
- ui.enableAutoTransition();
- ui.setFrames(clockFrame, 2);
- clockFrame[0] = drawClock;
- clockFrame[1] = drawWeather;
+ }
+ else {
+ if (NEWS_ENABLED) {
+ ui.enableAutoTransition();
+ ui.setFrames(clockFrame, 13);
+ clockFrame[0] = drawClock;
+ clockFrame[1] = drawNewsPage1;
+ clockFrame[2] = drawNewsPage2;
+ clockFrame[3] = drawNewsPage3;
+ if (DISPLAYWEATHER) {
+ clockFrame[4] = drawWeather;
+ } else {
+ clockFrame[4] = drawClock;
+ }
+ clockFrame[5] = drawNewsPage4;
+ clockFrame[6] = drawNewsPage5;
+ clockFrame[7] = drawNewsPage6;
+ clockFrame[8] = drawClock;
+ clockFrame[9] = drawNewsPage7;
+ clockFrame[10] = drawNewsPage8;
+ clockFrame[11] = drawNewsPage9;
+ clockFrame[12] = drawNewsPage10;
+ } else {
+ ui.enableAutoTransition();
+ ui.setFrames(clockFrame, 2);
+ clockFrame[0] = drawClock;
+ clockFrame[1] = drawWeather;
+ }
}
ui.setOverlays(clockOverlay, numberOfOverlays);
isClockOn = true;
@@ -1129,4 +1372,4 @@ void enableDisplay(boolean enable) {
Serial.println("Display was turned OFF: " + timeClient.getFormattedTime());
displayOffEpoch = lastEpoch;
}
-}
+}