ESticky: a Tiny Paperless Way to Keep Your Thoughts Organized
by gokux in Circuits > Gadgets
6684 Views, 93 Favorites, 0 Comments
ESticky: a Tiny Paperless Way to Keep Your Thoughts Organized


In a world where digital convenience meets minimalist style, e-paper displays offer a fantastic way to keep your notes, reminders, and to-do lists organised—without the mess of traditional sticky notes. Imagine having a reusable, low-power sticky note that updates through Wi-Fi!
This project turns the Waveshare 2.9-inch e-paper display and the Seeed Studio XIAO ESP32C3 into a sleek, always-visible sticky note that you can easily update using a web app. Whether you're jotting down daily tasks, sharing motivational quotes, or displaying fun images, this e-paper note keeps your space tidy and your ideas front and center.
Supplies

Enclosure Designing

I utilised Fusion 360 to plan and design my project, which required careful space optimisation. I needed to fit all the parts into the smallest form factor possible while ensuring practicality, including sufficient space for wiring and easy assembly. First, I imported all the 3D models of the parts and tried different configurations by placing the parts in various positions. Once I found the optimal configurations, I built the enclosure around them. All design files are provided below
3D Printing

I usually use FDM 3D printing for my enclosures, but for this project, I decided to try SLS printing. I used JLC3DP's 3D printing service, choosing 3201 PA-F Nylon for its grainy surface texture and 8001 Resin to experiment with a transparent look. Both prints turned out great, and I’m pleased with the results. This project was made possible with the support of JLC3DP
Order your Parts from the JLC3DP Link
Code
This project transforms the Seeed Studio XIAO ESP32C3 into a smart electronic sticky note using a 2.9-inch e-paper display. The device hosts a web app that allows users to input text and images, preview them, and send the final content to the e-paper screen. The web interface features a modern, minimalistic UI with a basic text editor and image upload functionality. The XIAO ESP32C3 processes the input, converts it into a bitmap format, and updates the e-paper display accordingly.
However, this is an early version (v0.1) of the project, and some issues remain, particularly with image upload handling and text alignment. As an open-source project, I welcome contributions from developers to improve functionality, fix bugs, and enhance the overall user experience.
Make sure you have the required libraries and replace the WIFI Password and SSID in the code
#include <GxEPD2_BW.h>
#include <WiFi.h>
#include <WebServer.h>
#include <LittleFS.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSerif9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSerif12pt7b.h>
// E-Paper Display Pins
#define CS_PIN D1
#define DC_PIN D3
#define RST_PIN D0
#define BUSY_PIN D5
// Initialize E-Paper Display
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> display(GxEPD2_290_BS(CS_PIN, DC_PIN, RST_PIN, BUSY_PIN));
// Wi-Fi Credentials
const char* ssid = "*********";
const char* password = "*********";
// Web Server
WebServer server(80);
// Default Font
const GFXfont* currentFont = &FreeMonoBold9pt7b;
uint16_t textColor = GxEPD_BLACK;
uint16_t textAlign = 0; // 0 = Left, 1 = Center, 2 = Right
// Function Prototypes
void handleRoot();
void handleUpdate();
void handleImageUpload();
void handleImageUploadFile();
void updateDisplay(String text);
void displayImage(const char* filename);
void displayIPAddress(String ip);
// Embedded Web App HTML with Modern UI
const char* html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-Paper Sticky Note</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
<h1 class="text-2xl font-bold mb-6">E-Paper Sticky Note</h1>
<form id="textForm" class="mb-6">
<label class="block text-sm font-medium mb-2">Text:</label>
<textarea id="text" name="text" class="w-full p-2 border rounded-md mb-4"></textarea>
<label class="block text-sm font-medium mb-2">Font:</label>
<select id="font" name="font" class="w-full p-2 border rounded-md mb-4">
<option value="mono9">Monospace (9pt)</option>
<option value="sans9">Sans-Serif (9pt)</option>
<option value="serif9">Serif (9pt)</option>
<option value="mono12">Monospace (12pt)</option>
<option value="sans12">Sans-Serif (12pt)</option>
<option value="serif12">Serif (12pt)</option>
</select>
<label class="block text-sm font-medium mb-2">Text Alignment:</label>
<select id="align" name="align" class="w-full p-2 border rounded-md mb-4">
<option value="0">Left</option>
<option value="1">Center</option>
<option value="2">Right</option>
</select>
<button type="submit" class="w-full bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600">Update Display</button>
</form>
<h2 class="text-xl font-bold mb-4">Upload Image</h2>
<form id="imageForm">
<input type="file" id="image" name="image" accept="image/bmp" class="mb-4">
<button type="submit" class="w-full bg-green-500 text-white p-2 rounded-md hover:bg-green-600">Upload Image</button>
</form>
</div>
<script>
const textForm = document.getElementById('textForm');
const imageForm = document.getElementById('imageForm');
textForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(textForm);
const response = await fetch('/update', {
method: 'POST',
body: new URLSearchParams(formData)
});
if (response.ok) {
alert("Display updated successfully!");
}
});
imageForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(imageForm);
const response = await fetch('/image', {
method: 'POST',
body: formData
});
if (response.ok) {
alert("Image uploaded successfully!");
}
});
</script>
</body>
</html>
)rawliteral";
void setup() {
Serial.begin(115200);
// Initialize E-Paper Display
display.init(115200, true, 50, false);
display.setRotation(1);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to Wi-Fi...");
}
Serial.println("Connected to Wi-Fi");
Serial.println(WiFi.localIP());
// Display IP Address on E-Paper
displayIPAddress(WiFi.localIP().toString());
// Initialize LittleFS
if (!LittleFS.begin(true)) {
Serial.println("Failed to mount LittleFS");
return;
}
// Serve the web app
server.on("/", HTTP_GET, handleRoot);
server.on("/update", HTTP_POST, handleUpdate);
server.on("/image", HTTP_POST, handleImageUpload, handleImageUploadFile);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
}
// Serve the web app
void handleRoot() {
server.send(200, "text/html", html);
}
// Handle text and font updates
void handleUpdate() {
String text = server.arg("text");
String font = server.arg("font");
textAlign = server.arg("align").toInt();
// Set the selected font
if (font == "mono9") {
currentFont = &FreeMonoBold9pt7b;
} else if (font == "sans9") {
currentFont = &FreeSans9pt7b;
} else if (font == "serif9") {
currentFont = &FreeSerif9pt7b;
} else if (font == "mono12") {
currentFont = &FreeMonoBold12pt7b;
} else if (font == "sans12") {
currentFont = &FreeSans12pt7b;
} else if (font == "serif12") {
currentFont = &FreeSerif12pt7b;
}
// Update the display
updateDisplay(text);
server.send(200, "text/plain", "Display updated");
}
// Handle image upload
void handleImageUpload() {
server.send(200, "text/plain", "Image uploaded");
}
void handleImageUploadFile() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
// Open the file for writing
File file = LittleFS.open("/image.bmp", "w");
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
// Write the received data to the file
File file = LittleFS.open("/image.bmp", "a");
if (file) {
file.write(upload.buf, upload.currentSize);
file.close();
}
} else if (upload.status == UPLOAD_FILE_END) {
// Display the uploaded image
displayImage("/image.bmp");
}
}
// Update the display with text
void updateDisplay(String text) {
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setFont(currentFont);
display.setTextColor(textColor);
// Calculate text position based on alignment
int16_t x = 0; // Default left alignment
int16_t y = 11;
if (textAlign == 1) { // Center alignment
x = (display.width() / 2) - (text.length() * 6); // Approximate center alignment
} else if (textAlign == 2) { // Right alignment
x = display.width() - (text.length() * 12) - 10; // Approximate right alignment
}
display.setCursor(x, y);
display.println(text);
} while (display.nextPage());
}
// Display an image on the E-Paper
void displayImage(const char* filename) {
File file = LittleFS.open(filename, "r");
if (!file) {
Serial.println("Failed to open image file");
return;
}
// Read the BMP file into a buffer
size_t fileSize = file.size();
uint8_t* buffer = (uint8_t*)malloc(fileSize);
if (!buffer) {
Serial.println("Failed to allocate memory for image");
file.close();
return;
}
file.read(buffer, fileSize);
file.close();
// Display the image
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.drawImage(buffer, 0, 0, display.width(), display.height(), false, false, false);
} while (display.nextPage());
// Free the buffer
free(buffer);
}
// Display IP Address on E-Paper
void displayIPAddress(String ip) {
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setFont(&FreeMonoBold9pt7b);
display.setTextColor(GxEPD_BLACK);
display.setCursor(10, 30);
display.println("IP Address:");
display.setCursor(10, 60);
display.println(ip);
} while (display.nextPage());
}
Assembly and Wiring

It is pretty simple, we are only using a few companies. Also, please note that we are using Xaio on board BMS to charge the battery
1. Started assembly by gluing the Xiao on the back panel with the antenna
2. Placed the e-paper display module and glued the slide switch onto the 3d printed slot
3. Glue the battery and connect the wires to the switch and XIAO battery input
4. Then I completed the rest of the wiring according to the circuit diagram provided
5. Then I used the screws that came with the e-paper module to secure everything.
Operation

We have completed our build, so let's power it up. Turn on the device. If the Wi-Fi details you entered are correct, it will connect to your Wi-Fi network and display an IP address. Enter that IP address into your browser to ensure that the controlling device is also connected to the same network.
On the control page, you can type the text that you want to be displayed on the device. You also have the option to change the fonts. After typing your message, press the "Update Display" button to refresh the content on e-stiky. You can also save the battery by switching off the device after the Display update
*****I am currently experiencing some issues with text alignment and the image upload feature, but these will be resolved soon.****
Final Thought

As you know, there are more possibilities for this project. I have more plans for the new version of this project: a control app, Bluetooth connectivity, image bitmap, more energy-optimized code, custom PCB, and a larger battery. So just follow my Instagram page for more updates