adafruit_feather_quote.ino
1 // SPDX-FileCopyrightText: 2019 Dan Cogliano for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 /*************************************************** 6 * Quote Display for Adafruit ePaper FeatherWings 7 * For use with Adafruit tricolor and monochrome ePaper FeatherWings 8 * 9 * Adafruit invests time and resources providing this open source code. 10 * Please support Adafruit and open source hardware by purchasing 11 * products from Adafruit! 12 * 13 * Written by Dan Cogliano for Adafruit Industries 14 * Copyright (c) 2019 Adafruit Industries 15 * 16 * Notes: 17 * Update the secrets.h file with your WiFi details 18 * Uncomment the ePaper display type you are using below. 19 * Change the SLEEP setting to define the time between quotes 20 */ 21 22 #include <Adafruit_GFX.h> // Core graphics library 23 #include <HTTPClient.h> 24 #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson 25 #include <Adafruit_EPD.h> 26 #include "secrets.h" 27 28 // define the # of seconds to sleep before waking up and getting a new quote 29 #define SLEEP 3600 // 1 hour in seconds 30 31 // WiFi timeout in seconds 32 #define WIFI_TIMEOUT 30 33 34 // What fonts do you want to use? 35 #include <Fonts/FreeSans9pt7b.h> 36 //#include <Fonts/FreeSans12pt7b.h> // a font option for larger screens 37 38 // qfont is the font for the quote 39 const GFXfont *qfont = &FreeSans9pt7b; 40 //const GFXfont *qfont = &FreeSans12pt7b; 41 42 // afont is the font for the author's name 43 const GFXfont *afont = &FreeSans9pt7b; 44 //const GFXfont *afont = &FreeSans12pt7b; 45 46 // ofont is the font for the origin of the feed 47 const GFXfont *ofont = NULL; 48 49 // font to use if qfont is too large 50 // NULL means use the system font (which is small) 51 const GFXfont *smallfont = NULL; 52 53 54 // ESP32 settings 55 #define SD_CS 14 56 #define SRAM_CS 32 57 #define EPD_CS 15 58 #define EPD_DC 33 59 #define LEDPIN 13 60 #define LEDPINON HIGH 61 #define LEDPINOFF LOW 62 63 #define EPD_RESET -1 // can set to -1 and share with microcontroller Reset! 64 #define EPD_BUSY -1 // can set to -1 to not use a pin (will wait a fixed delay) 65 66 // Uncomment the following line if you are using 2.13" tricolor 212*104 EPD 67 //Adafruit_IL0373 epd(212, 104 ,EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY); 68 // Uncomment the following line if you are using 2.13" monochrome 250*122 EPD 69 Adafruit_SSD1675 epd(250, 122, EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY); 70 71 // get string length in pixels 72 // set text font prior to calling this 73 int getStringLength(const char *str, int strlength = 0) 74 { 75 char buff[1024]; 76 int16_t x, y; 77 uint16_t w, h; 78 if(strlength == 0) 79 { 80 strcpy(buff, str); 81 } 82 else 83 { 84 strncpy(buff, str, strlength); 85 buff[strlength] = '\0'; 86 } 87 epd.getTextBounds(buff, 0, 0, &x, &y, &w, &h); 88 return(w); 89 } 90 91 // word wrap routine 92 // first time send string to wrap 93 // 2nd and additional times: use empty string 94 // returns substring of wrapped text. 95 char *wrapWord(const char *str, int linesize) 96 { 97 static char buff[1024]; 98 int linestart = 0; 99 static int lineend = 0; 100 static int bufflen = 0; 101 if(strlen(str) == 0) 102 { 103 // additional line from original string 104 linestart = lineend + 1; 105 lineend = bufflen; 106 Serial.println("existing string to wrap, starting at position " + String(linestart) + ": " + String(&buff[linestart])); 107 } 108 else 109 { 110 Serial.println("new string to wrap: " + String(str)); 111 memset(buff,0,sizeof(buff)); 112 // new string to wrap 113 linestart = 0; 114 strcpy(buff,str); 115 lineend = strlen(buff); 116 bufflen = strlen(buff); 117 } 118 uint16_t w; 119 int lastwordpos = linestart; 120 int wordpos = linestart + 1; 121 while(true) 122 { 123 while(buff[wordpos] == ' ' && wordpos < bufflen) 124 wordpos++; 125 while(buff[wordpos] != ' ' && wordpos < bufflen) 126 wordpos++; 127 if(wordpos < bufflen) 128 buff[wordpos] = '\0'; 129 w = getStringLength(&buff[linestart]); 130 if(wordpos < bufflen) 131 buff[wordpos] = ' '; 132 if(w > linesize) 133 { 134 buff[lastwordpos] = '\0'; 135 lineend = lastwordpos; 136 return &buff[linestart]; 137 } 138 else if(wordpos >= bufflen) 139 { 140 // first word too long or end of string, send it anyway 141 buff[wordpos] = '\0'; 142 lineend = wordpos; 143 return &buff[linestart]; 144 } 145 lastwordpos = wordpos; 146 wordpos++; 147 } 148 } 149 150 // return # of lines created from word wrap 151 int getLineCount(const char *str, int scrwidth) 152 { 153 int linecount = 0; 154 String line = wrapWord(str,scrwidth); 155 156 while(line.length() > 0) 157 { 158 linecount++; 159 line = wrapWord("",scrwidth); 160 } 161 return linecount; 162 } 163 164 int getLineHeight(const GFXfont *font = NULL) 165 { 166 int height; 167 if(font == NULL) 168 { 169 height = 12; 170 } 171 else 172 { 173 height = (uint8_t)pgm_read_byte(&font->yAdvance); 174 } 175 return height; 176 } 177 178 // Retrieve page response from given URL 179 String getURLResponse(String url) 180 { 181 HTTPClient http; 182 String jsonstring = ""; 183 Serial.println("getting url: " + url); 184 if(http.begin(url)) 185 { 186 Serial.print("[HTTP] GET...\n"); 187 // start connection and send HTTP header 188 int httpCode = http.GET(); 189 190 // httpCode will be negative on error 191 if (httpCode > 0) { 192 // HTTP header has been sent and Server response header has been handled 193 Serial.println("[HTTP] GET... code: " + String(httpCode)); 194 195 // file found at server 196 if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { 197 jsonstring = http.getString(); 198 // use this string for testing very long quotes 199 //jsonstring = "[{\"text\":\"Don't worry about what anybody else is going to do… The best way to predict the future is to invent it. Really smart people with reasonable funding can do just about anything that doesn't violate too many of Newton's Laws!\",\"author\":\"Alan Kay\"}]"; 200 Serial.println(jsonstring); 201 } 202 } else { 203 Serial.println("[HTTP] GET... failed, error: " + http.errorToString(httpCode)); 204 } 205 http.end(); 206 } 207 else { 208 Serial.println("[HTTP] Unable to connect"); 209 } 210 return jsonstring; 211 } 212 213 void getQuote(String "e, String &author) 214 { 215 StaticJsonDocument<1024> doc; 216 String url = "https://www.adafruit.com/api/quotes.php"; 217 String jsonquote = getURLResponse(url); 218 if(jsonquote.length() > 0) 219 { 220 // remove start and end brackets, jsonBuffer is confused by them 221 jsonquote = jsonquote.substring(1,jsonquote.length()-1); 222 Serial.println("using: " + jsonquote); 223 DeserializationError error = deserializeJson(doc, jsonquote); 224 if (error) 225 { 226 Serial.println("json parseObject() failed"); 227 Serial.println("bad json: " + jsonquote); 228 quote = "json parseObject() failed"; 229 } 230 else 231 { 232 String tquote = doc["text"]; 233 String tauthor = doc["author"]; 234 quote = tquote; 235 author = tauthor; 236 } 237 } 238 else 239 { 240 quote = "Error retrieving URL"; 241 } 242 } 243 244 void printQuote(String "e) 245 { 246 int x = 0; 247 int y = 0; 248 bool bsmallfont = false; 249 epd.setTextColor(EPD_BLACK); 250 epd.setFont(qfont); 251 epd.setTextSize(1); 252 253 int scrwidth = epd.width() - 8; 254 Serial.println("Screen width is " + String(scrwidth)); 255 Serial.println("Screen height is " + String(epd.height())); 256 int linecount = getLineCount(quote.c_str(),scrwidth); 257 int lineheightquote = getLineHeight(qfont); 258 int lineheightauthor = getLineHeight(afont); 259 int lineheightother = getLineHeight(ofont); 260 int maxlines = (epd.height() - (lineheightauthor + lineheightother)) / lineheightquote; 261 Serial.println("maxlines is " + String(maxlines)); 262 Serial.println("line height is " +String(lineheightquote)); 263 Serial.println("linecount is " +String(linecount)); 264 int topmargin = 0; 265 if(linecount > maxlines) 266 { 267 // too long for default font size 268 // next attempt, reduce lineheight to .8 size 269 lineheightquote = .8 * lineheightquote; 270 maxlines = (epd.height() - (lineheightauthor + lineheightother)) / lineheightquote; 271 if(linecount > maxlines) 272 { 273 // next attempt, use small font 274 epd.setFont(smallfont); 275 bsmallfont = true; 276 epd.setTextSize(1); 277 lineheightquote = getLineHeight(smallfont); 278 maxlines = (epd.height() - (lineheightauthor + lineheightother)) / lineheightquote; 279 linecount = getLineCount(quote.c_str(),scrwidth); 280 if(linecount > maxlines) 281 { 282 // final attempt, last resort is to reduce the lineheight to make it fit 283 lineheightquote = (epd.height() - (lineheightauthor + lineheightother)) / linecount; 284 } 285 } 286 Serial.println("maxlines has changed to " + String(maxlines)); 287 Serial.println("line height has changed to " +String(lineheightquote)); 288 Serial.println("linecount has changed to " +String(linecount)); 289 } 290 if(linecount <= maxlines) 291 { 292 293 topmargin = (epd.height() - (lineheightauthor + lineheightother) - linecount*lineheightquote)/2; 294 if(!bsmallfont) 295 topmargin+=lineheightquote-4; 296 //Serial.println("topmargin = " + String(topmargin)); 297 } 298 String line = wrapWord(quote.c_str(),scrwidth); 299 300 int counter = 0; 301 epd.setTextColor(EPD_BLACK); 302 while(line.length() > 0) 303 { 304 counter++; 305 Serial.println("printing line " + String(counter) + ": '" + line + String("'")); 306 epd.setCursor(x +4, y + topmargin); 307 epd.print(line); 308 y += lineheightquote; 309 line = wrapWord("",scrwidth); 310 } 311 } 312 313 void printAuthor(String author) 314 { 315 epd.setTextColor(EPD_BLACK); 316 epd.setFont(afont); 317 int lineheightauthor = getLineHeight(afont); 318 int lineheightother = getLineHeight(ofont); 319 int x = getStringLength(author.c_str()); 320 // draw line above author 321 epd.drawLine(epd.width() - x - 10, epd.height() - (lineheightauthor + lineheightother) + 2, epd.width(), epd.height() - (lineheightauthor + lineheightother) + 2, EPD_RED); 322 epd.drawLine(epd.width() - x - 10, epd.height() - (lineheightauthor + lineheightother) + 2, epd.width() - x - 10,epd.height() - lineheightother - lineheightauthor/3, EPD_RED); 323 epd.drawLine(0, epd.height() - lineheightother - lineheightauthor/3, epd.width() - x - 10,epd.height() - lineheightother - lineheightauthor/3, EPD_RED); 324 // draw author text 325 int cursorx = epd.width() - x - 4; 326 int cursory = epd.height() - lineheightother - 2; 327 if(afont == NULL) 328 { 329 cursory = epd.height() - lineheightother - lineheightauthor - 2 ; 330 } 331 epd.setCursor(cursorx, cursory); 332 epd.print(author); 333 } 334 335 void printOther(String other) 336 { 337 epd.setFont(ofont); 338 int lineheightother = getLineHeight(ofont); 339 int ypos = epd.height() - 2; 340 if (ofont == NULL) 341 ypos = epd.height() - (lineheightother - 2); 342 epd.setTextColor(EPD_BLACK); 343 epd.setCursor(4,ypos); 344 epd.print(other); 345 } 346 347 void setup() { 348 Serial.begin(115200); 349 //while(!Serial); 350 351 pinMode(LEDPIN, OUTPUT); 352 digitalWrite(LEDPIN, LEDPINON); 353 354 epd.begin(); 355 Serial.println("ePaper display initialized"); 356 epd.clearBuffer(); 357 epd.setTextWrap(false); 358 359 Serial.print("Connecting to WiFi "); 360 WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 361 int counter = 0; 362 while (WiFi.status() != WL_CONNECTED && counter < WIFI_TIMEOUT) { 363 delay(1000); 364 Serial.print("."); 365 counter++; 366 } 367 368 String quote, author; 369 if(WiFi.status() == WL_CONNECTED) 370 { 371 Serial.println("connected"); 372 getQuote(quote, author); 373 } 374 else 375 { 376 quote = "WiFi connection timed out, try again later"; 377 Serial.println(quote); 378 } 379 380 printQuote(String("\"") + quote + String("\"")); 381 printAuthor(author); 382 printOther("adafruit.com/quotes"); 383 384 epd.display(); 385 Serial.println("done, going to sleep..."); 386 // power down ePaper display 387 epd.powerDown(); 388 // put microcontroller to sleep, wake up after specified time 389 ESP.deepSleep(SLEEP * 1e6); 390 } 391 392 void loop() { 393 // should never get here, setup() puts the CPU to sleep 394 }