Skip to content

Commit 314dfd2

Browse files
committed
Repo on the way
0 parents  commit 314dfd2

File tree

230 files changed

+178992
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

230 files changed

+178992
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.pio
2+
.vscode/.browse.c_cpp.db*
3+
.vscode/c_cpp_properties.json
4+
.vscode/launch.json
5+
.vscode/ipch
6+
.pioenvs
7+
.piolibdeps
8+
*.out

.vscode/extensions.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
// See http://go.microsoft.com/fwlink/?LinkId=827846
3+
// for the documentation about the extensions.json format
4+
"recommendations": [
5+
"platformio.platformio-ide"
6+
],
7+
"unwantedRecommendations": [
8+
"ms-vscode.cpptools-extension-pack"
9+
]
10+
}

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
### This project displays a daily C++ quiz question from cppquiz.org on an e-paper display powered by an ESP32 microcontroller. Questions are fetched via Google Apps Script connected to a Google Sheets backend, then rendered with clean formatting on a 7.5" E-Ink screen.
2+
3+
<p align="center">
4+
<img src="photos/onwifi.png" alt="E-paper device showing C++ quiz" width="400"/>
5+
</p>
6+
7+
## 🚀 Features
8+
9+
- Displays C++ quiz question from a Google Sheet
10+
- Works offline by caching the last successful question
11+
- Uses deep sleep to save power (wakes up every hour)
12+
- Shows Wi-Fi status for shown question (connected/disconnected) with an icon
13+
- Fully customizable via Google Sheets + Apps Script backend
14+
15+
16+
## 📦 Hardware
17+
18+
- ESP32
19+
- Waveshare ESP32 Epaper Driver Board
20+
- Waveshare 7.5" E-Paper Display
21+
22+
23+
## 🧠 How It Works
24+
25+
- ESP32 boots and connects to Wi-Fi.
26+
- It sends an HTTP request to a Google Apps Script endpoint.
27+
- The Apps Script reads a unshown question from Google Sheets and returns it as plain text.
28+
- ESP32 displays the question on the e-paper screen.
29+
- If Wi-Fi fails, it loads the last cached question.
30+
- Device goes into deep sleep and repeats after one hour.
31+
32+
33+
## 🎉 To Build Yourself
34+
35+
- ### Software
36+
37+
- Upload an Excel file ([questions.xlsx](cppquiz_questions.xlsx)) to Google Drive
38+
- Open it with Google Sheets
39+
- In your Google Sheet:
40+
- Click Extensions > Apps Script
41+
- Paste the code.gs ([code.gs](code.gs))
42+
- Deploy the script as a Web App
43+
- Set access to "Anyone with the link"
44+
-
45+
Update `code.gs` ([code.gs](code.gs)):
46+
```js
47+
var sheet_id = "YOUR_GOOGLE_SHEET_ID"; //Excel
48+
```
49+
50+
51+
- ### Hardware
52+
53+
Update `config.h` ([config.h](include/config.h))on ESP32 firmware:
54+
55+
```cpp
56+
const std::string WIFI_SSID = "YOUR_WIFI_SSID";
57+
const std::string WIFI_PWD = "YOUR_WIFI_PWD"
58+
const std::string GS_DEPLOYMENT_ID = "YOUR_GS_DEPLOYMENT_ID"
59+
```
60+
61+
## 📄 Why Use an Excel File?
62+
63+
The questions displayed on this device are sourced from [cppquiz.org](https://cppquiz.org/), which provides a comprehensive collection of C++ questions. However, the site does not offer an API to fetch individual questions by ID.
64+
65+
Instead, all questions are made available as a bulk JSON file at:
66+
67+
https://static.cppquiz.org/published.json
68+
69+
Due to the size of this dataset and the need for easier navigation, filtering, and tracking of displayed questions, I chose to manage the content through a Google Sheets with Google App Script.
70+
71+
72+
73+
<a href="https://buymeacoffee.com/vvolkanunas" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>

code.gs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
var sheet_id = "YOUR_GOOGLE_SHEET_ID"; // Google Sheet ID
2+
var ss = SpreadsheetApp.openById(sheet_id);
3+
var sheet = ss.getSheets()[0]; // Get the first sheet in the spreadsheet
4+
5+
/**
6+
* Handles HTTP GET requests.
7+
* Expected parameter: ?read=question
8+
*/
9+
function doGet(e) {
10+
var arg = e.parameter.read;
11+
12+
// If no valid parameter is provided
13+
if (arg == undefined){
14+
return ContentService.createTextOutput("error: invalid request");
15+
}
16+
17+
// If the request is for a question, return a new question
18+
if (arg == "question"){
19+
return getIter();
20+
}
21+
22+
// For any other unexpected request
23+
return ContentService.createTextOutput("error: unexpected request");
24+
}
25+
26+
/**
27+
* Finds the next question, marks it as shown, and returns its data.
28+
*/
29+
function getIter() {
30+
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
31+
32+
var row = 2; // Start checking from row 2 (assuming row 1 is header)
33+
34+
// Find the first row in column H without a checkmark
35+
while (sheet.getRange("H" + row).getValue() !== "") {
36+
row++;
37+
}
38+
39+
// Get question data from columns A, B, and G for the current row
40+
var question = sheet.getRange("B" + row).getDisplayValue();
41+
var difficulty = sheet.getRange("G" + row).getDisplayValue();
42+
var question_id = sheet.getRange("A" + row).getDisplayValue();
43+
44+
// Mark this question as used by putting a checkmark in column H
45+
sheet.getRange("H" + row).setValue("✓");
46+
47+
// Prepare the response string
48+
var res = "Question #" + question_id + " Difficulty : " + difficulty + "\n\n" + question;
49+
50+
Logger.log(res);
51+
52+
// Return the result as a plain text HTTP response
53+
return ContentService.createTextOutput(res);
54+
}

cppquiz_questions.xlsx

84.9 KB
Binary file not shown.

include/assets/wifi.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifndef NO_WIFI_H
2+
#define NO_WIFI_H
3+
4+
#include <Arduino.h>
5+
6+
// 48 x 48
7+
const unsigned char wifi_on_48x48[] PROGMEM = {
8+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
9+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
10+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
11+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
12+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff,
13+
0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff,
14+
0xff, 0x80, 0x1f, 0xf8, 0x01, 0xff, 0xff, 0x01, 0xff, 0xff, 0x80, 0xff,
15+
0xfc, 0x0f, 0xff, 0xff, 0xf0, 0x3f, 0xf8, 0x3f, 0xff, 0xff, 0xfc, 0x1f,
16+
0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0xe1, 0xff, 0xff, 0xff, 0xff, 0x87,
17+
0xe3, 0xff, 0xe0, 0x07, 0xff, 0xcf, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
18+
0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x0f, 0xf0, 0x0f, 0xff,
19+
0xff, 0xc0, 0xff, 0xff, 0x03, 0xff, 0xff, 0x83, 0xff, 0xff, 0xc1, 0xff,
20+
0xff, 0x87, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xfb, 0xff,
21+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff,
22+
0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
23+
0xff, 0xfe, 0x07, 0xe0, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xfc, 0x7f, 0xff,
24+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
25+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
26+
0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff,
27+
0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff,
28+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
29+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
30+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
31+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
32+
};
33+
34+
// 48 x 48
35+
const unsigned char wifi_off_48x48[] PROGMEM = {
36+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
37+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
38+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
39+
0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff,
40+
0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xe0, 0x03, 0xff, 0xff,
41+
0xff, 0xc3, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xe1, 0xe0, 0x00, 0x0f, 0xff,
42+
0xff, 0xc0, 0xff, 0xf8, 0x01, 0xff, 0xff, 0x00, 0xff, 0xff, 0x80, 0xff,
43+
0xfc, 0x08, 0x7f, 0xff, 0xf0, 0x3f, 0xf8, 0x3c, 0x3f, 0xff, 0xfc, 0x1f,
44+
0xe0, 0xfe, 0x1f, 0xff, 0xff, 0x07, 0xe1, 0xff, 0x0f, 0xff, 0xff, 0x87,
45+
0xe3, 0xff, 0x87, 0xf7, 0xff, 0xcf, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff,
46+
0xff, 0xfc, 0x01, 0xe0, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0xf0, 0x0f, 0xff,
47+
0xff, 0xc0, 0xf8, 0x7f, 0x03, 0xff, 0xff, 0x83, 0xfc, 0x3f, 0xc1, 0xff,
48+
0xff, 0x87, 0xfc, 0x3f, 0xe1, 0xff, 0xff, 0xdf, 0xfe, 0x1f, 0xfb, 0xff,
49+
0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff,
50+
0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff,
51+
0xff, 0xfe, 0x07, 0xe0, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xf8, 0x7f, 0xff,
52+
0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
53+
0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff,
54+
0xff, 0xff, 0xfe, 0x7f, 0x87, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xc3, 0xff,
55+
0xff, 0xff, 0xfc, 0x3f, 0xe1, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xf0, 0xff,
56+
0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff,
57+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
58+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
59+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
60+
};
61+
62+
#endif

include/config.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef CONFIG_H
2+
#define CONFIG_H
3+
4+
#include <string>
5+
6+
#define WS_BOARD
7+
8+
const uint8_t PIN_EPD_BUSY = 25;
9+
const uint8_t PIN_EPD_CS = 15;
10+
const uint8_t PIN_EPD_RST = 26;
11+
const uint8_t PIN_EPD_DC = 27;
12+
13+
// WiFi credentials
14+
const std::string WIFI_SSID = "YOUR_WIFI_SSID";
15+
const std::string WIFI_PWD = "YOUR_WIFI_PWD";
16+
17+
// Google Apps Script Deployment ID
18+
const std::string GS_DEPLOYMENT_ID = "YOUR_GS_DEPLOYMENT_ID";
19+
// Base URL for accessing the Google Apps Script
20+
const std::string BASE_URL = "https://script.google.com/macros/s/" + GS_DEPLOYMENT_ID + "/exec?read=";
21+
22+
// Query parameter for requesting a question
23+
const std::string QUESTION_TAG = "question";
24+
// Final URL used to fetch a question
25+
const std::string QUESTION_URL = BASE_URL + QUESTION_TAG;
26+
27+
// Sleep duration in us
28+
constexpr uint64_t SLEEP_DURATION_US = 60ULL * 1000'000 * 60;
29+
30+
31+
#endif

include/display_utils.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#ifndef DISPLAY_UTILS_H
2+
#define DISPLAY_UTILS_H
3+
4+
#include <GxEPD2_BW.h>
5+
#include <SPI.h>
6+
7+
#include "assets/wifi.h"
8+
#include "fonts/FreeSans.h"
9+
#include "fonts/UbuntuMono_R.h"
10+
#include "config.h"
11+
12+
// Macro to disallow copying and assignment for the Display class
13+
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
14+
TypeName(const TypeName&) = delete; \
15+
void operator=(const TypeName&) = delete;
16+
17+
// Generic Display class template for rendering content to an e-ink display
18+
template <typename DisplayType>
19+
class Display
20+
{
21+
public:
22+
23+
// Constructor initializes display and SPI if on WS_BOARD
24+
Display()
25+
: m_display(DisplayType(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY))
26+
#ifdef WS_BOARD
27+
, m_hspi(HSPI)
28+
#endif
29+
{}
30+
31+
// Initializes display and sets rotation
32+
void begin()
33+
{
34+
#ifdef WS_BOARD
35+
// Initialize SPI with custom pins (specific to WS_BOARD)
36+
m_hspi.begin(13, 12, 14, 15);
37+
m_display.epd2.selectSPI(m_hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
38+
#endif
39+
m_display.init(115200);
40+
m_display.setRotation(1);
41+
m_display.setTextSize(1);
42+
m_display.setTextColor(GxEPD_BLACK);
43+
m_display.fillScreen(GxEPD_WHITE);
44+
}
45+
46+
// Renders the full display including header, question, WiFi icon, and content
47+
void render(const String& ret, const bool connection_status)
48+
{
49+
printHeader(); // Display the title
50+
printQuestion(); // Display the prompt question
51+
52+
// Display WiFi icon depending on connection status
53+
displayBitmap(connection_status ? wifi_on_48x48 : wifi_off_48x48);
54+
55+
// Display the question content
56+
m_display.setFont(&UbuntuMono_R_12pt7b);
57+
m_display.setCursor(0, 150);
58+
m_display.print(ret);
59+
60+
m_display.display();
61+
m_display.powerOff();
62+
}
63+
64+
private:
65+
66+
// Draws the WiFi icon bitmap (connected or disconnected)
67+
void displayBitmap(const uint8_t* bitmap)
68+
{
69+
m_display.drawInvertedBitmap(DisplayType::HEIGHT - 48, 0, bitmap, 48, 48, GxEPD_BLACK);
70+
}
71+
72+
// Displays the header text centered at the top of the screen
73+
void printHeader()
74+
{
75+
m_display.setFont(&FreeSans_16pt7b);
76+
const char Header[] = "C++ Quiz - cppquiz.org!";
77+
int16_t tbx, tby;
78+
uint16_t tbw, tbh;
79+
m_display.getTextBounds(Header, 0, 0, &tbx, &tby, &tbw, &tbh);
80+
uint16_t x = ((m_display.width() - tbw) / 2) - tbx;
81+
m_display.setCursor(x, tbh);
82+
m_display.print(Header);
83+
}
84+
85+
// Displays the static question prompt
86+
void printQuestion()
87+
{
88+
m_display.setFont(&FreeSans_12pt7b);
89+
const char Question[] = "According to the C++23 standard, what is the output of this program?";
90+
m_display.setCursor(0, 75);
91+
m_display.print(Question);
92+
}
93+
94+
using EinkDisplay = GxEPD2_BW<DisplayType, DisplayType::HEIGHT>;
95+
96+
#ifdef WS_BOARD
97+
SPIClass m_hspi; // SPI instance for WS_BOARD
98+
#endif
99+
EinkDisplay m_display; // E-ink display driver
100+
101+
DISALLOW_COPY_AND_ASSIGN(Display)
102+
};
103+
104+
#endif
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef __FONTS_ALLERTASTENCIL_REGULAR_H__
2+
#define __FONTS_ALLERTASTENCIL_REGULAR_H__
3+
#include "AllertaStencil_Regular/AllertaStencil_Regular_12pt7b.h"
4+
#include "AllertaStencil_Regular/AllertaStencil_Regular_16pt7b.h"
5+
#include "AllertaStencil_Regular/AllertaStencil_Regular_20pt7b.h"
6+
#include "AllertaStencil_Regular/AllertaStencil_Regular_26pt7b.h"
7+
#include "AllertaStencil_Regular/AllertaStencil_Regular_32pt7b.h"
8+
#include "AllertaStencil_Regular/AllertaStencil_Regular_40pt7b.h"
9+
#include "AllertaStencil_Regular/AllertaStencil_Regular_48pt7b.h"
10+
#include "AllertaStencil_Regular/AllertaStencil_Regular_6pt7b.h"
11+
#endif

0 commit comments

Comments
 (0)