NotiFY

by AahanSharma in Circuits > Microcontrollers

5839 Views, 18 Favorites, 0 Comments

NotiFY

VID-20260413-WA0012.gif
IMG_20260413_115807_Bokeh.jpg
IMG_20260413_113551_Bokeh.jpg

Even though I was working outside in the middle of nowhere, I kept taking my phone out of my pocket. I was waiting for some important messages, but it's a terrible habit. I would lose concentration every time my phone buzzed, take it out, and nothing would happen. Just another newsletter, spam, or an arbitrary Mail notification. It totally destroyed my workflow and was extremely distracting.

I came to the conclusion that I needed a method to block out the background noise. I couldn't afford to keep checking my phone every two minutes, but I also didn't want to turn it off entirely. What if I could create a physical alert system that is entirely independent? Something that actively "taps me on the shoulder" only when a VIP or a truly important message arrives, while remaining completely silent and dark and out of the way?

I chose to create my own hardware solution to address the issue rather than purchasing a pricey smartwatch or downloading yet another focus app. I wanted to create a small, distraction-free gadget that would only capture the important things, trapping all of the spam on my phone.

Here's the tale of how I created my own physical focus-saver—and how you can do the same—if you have trouble with the "phantom buzz" or simply want to regain your deep work focus without missing important updates.

Supplies

IMG_20260412_215035.jpg

Supplies You Will Need:

  1. M5Stack AtomS3
  2. 3.7V LiPo Battery
  3. 3D Printed Partts

Software & Account (Open Source):

  1. Arduino IDE
  2. Make.com Account
  3. Adafruit IO Account

3D Model

WhatsApp Image 2026-04-12 at 17.58.43.jpeg
WhatsApp Image 2026-04-12 at 17.58.43 (1).jpeg
WhatsApp Image 2026-04-12 at 17.58.43 (2).jpeg
WhatsApp Image 2026-04-12 at 17.58.42.jpeg
WhatsApp Image 2026-04-12 at 17.58.42 (1).jpeg

I started by importing the 3D model of the M5Stack Atom into Fusion 360, then designed a custom enclosure around it. The enclosure consists of two parts—a top body and a bottom body—that come together to sandwich the M5Stack securely in place. Both parts are fastened using M2 screws, with mounting holes designed on the top and bottom sections.


On the top side, I included an opening for the USB Type-C port, allowing easy access for charging or reprogramming the device without disassembly. I also added a circular loop to attach a chain, so the device can be used as a wearable or hung easily.


After finalizing the design, I exported the mesh files for both enclosure parts and 3D printed them using orange Hyper PLA and black Hyper PLA.

Set Up (Software)

Screenshot 2026-04-12 at 11.32.34 PM.png
Screenshot 2026-04-12 at 11.32.53 PM.png
Screenshot 2026-04-12 at 11.33.11 PM.png

Our AtomS3 is a smart little device, but we don't want it constantly logging into Google every 5 seconds to check for new emails. That would drain power and hit API limits instantly. Instead, we want the device to sit quietly and wait for a signal.

To do this, we use Adafruit IO. It uses a super lightweight protocol called MQTT, which is basically the standard language for IoT devices. It acts as the "bridge" between the internet and our hardware.

How to set it up:

  1. Go to io.adafruit.com and create a free account.
  2. In the top menu, click on Feeds and then click + New Feed.
  3. Name your feed something simple, like atoms3-alerts, and click Create. (This feed is the exact "channel" your AtomS3 will tune into).
  4. Now, look for the yellow button with a Key icon (usually in the top right corner). Click it.
  5. A window will pop up showing your Username and your Active Key. Keep this window open or paste these into a notepad—you will need them for both Make.com and your Arduino code!

(Gmail & Make.com)

Screenshot 2026-04-12 at 6.05.43 PM.png
Screenshot 2026-04-12 at 8.06.12 PM.png
Screenshot 2026-04-12 at 8.06.30 PM.png

We now require a method to receive and forward critical emails via our Adafruit bridge. Make.com, a free automation tool, will serve as the intermediary.

Section A: Make.com's The Catcher

  1. Register for a free Make.com account and click Create a new scenario.
  2. Click the enormous "+" button, look up Webhooks, and choose Custom Mailhook.
  3. After selecting "Add," give it a name, and press "Save."
  4. Make.com will immediately produce an extremely lengthy and disorganized email address, such as hwdc8jr6...@hook.eu1.make.com. This address is copied.

Part B: The Filter (Gmail) If you skip this section, your AtomS3 will flash for each piece of spam you receive!

  1. On a computer, launch Gmail and select Settings (the gear icon) > See all settings.
  2. Open the POP/IMAP and Forwarding tab.
  3. Paste that lengthy Make.com address after selecting Add a forwarding address. (Make.com will receive a confirmation code from Gmail; simply click "Run once" in Make.com to receive the code, then paste it back into Gmail to confirm).
  4. Click Create a new filter under the Filters and Blocked Addresses tab.
  5. Here's where the magic takes place: Establish VIP guidelines. The "From" field allows you to input specific email addresses, such as your spouse, your boss, or your best client.
  6. Click the "Create filter," choose your Make.com address, and check the "Forward it to!" box.

Section C: Linking the Bridge

  1. Return to Make.com now. To add a second module, click the tiny "ear" on your Webhook circle and drag it out.
  2. Choose Make a request after searching for HTTP. Adafruit will speak with this module.
  3. Prepare it precisely as follows:
  4. URL: https://io.adafruit.com/api/v2/YOUR_USERNAME/feeds/atoms3-alerts/data (insert your real Adafruit username in place of YOUR_USERNAME).
  5. Process: POST
  6. Headers: Include a piece. Paste your Adafruit Active Key as the value and give it the name X-AIO-Key.
  7. Type of body: Raw
  8. JSON (application/json) content type
  9. Request information: Paste the code below, but drag and drop the Sender and Subject variables from your Mailhook into the values using the Make.com popup menu:

JSON.

{
"value": "{\"app\":\"GMAIL\", \"sender\":\"{{Sender}}\", \"subject\":\"{{Subject}}\", \"snippet\":\"null\"}"
}
  1. Click OK, save your scenario, and flip the Scheduling switch to ON at the bottom left.

The trap has been set. Gmail sends emails from VIPs to Make.com, which then packages them into a tidy JSON file and sends it to Adafruit IO. All we have to do now is program the AtomS3 to catch it!

Programming the AtomS3

Screenshot 2026-04-12 at 11.40.18 PM.png

We now have to program our little AtomS3 to draw those amazing user interface screens, connect to your WiFi, and listen to the Adafruit bridge.

Configure the Arduino IDE:

  1. Install the free Arduino IDE after downloading it.
  2. In order for your computer to recognize the AtomS3, you must install the M5Stack board package and add the M5Stack board manager URL in your preferences if you have never used an M5Stack board. If you run into trouble, you can refer to M5Stack's official quick-start guide.
  3. Install the following three necessary libraries by selecting Sketch > Include Library > Manage Libraries:
  4. (by M5Stack) M5AtomS3
  5. PubSubClient, created by Nick O'Leary
  6. Benoit Blanchon created ArduinoJson.

The Code: Use a USB-C cable to connect your AtomS3 to your computer. Paste the following code into your Arduino IDE after copying it.

⚠️ Essential: The credentials at the top of the code (Lines 7 through 12) must be changed before you hit upload. Enter your Adafruit username, Adafruit Active Key, and WiFi name and password.

In C++

#include "M5AtomS3.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// --- 1. NETWORK & CLOUD SETTINGS (CHANGE THESE!) ---
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "io.adafruit.com";
const char* mqtt_user = "YOUR_ADAFRUIT_USERNAME";
const char* mqtt_pass = "YOUR_ADAFRUIT_KEY";
const char* mqtt_topic = "YOUR_ADAFRUIT_USERNAME/feeds/atoms3-alerts";

WiFiClient espClient;
PubSubClient client(espClient);

// --- 2. STATE MACHINE & TIMERS ---
enum DeviceState { STATE_IDLE, STATE_WARNING_ANIMATION, STATE_ALERT_ACTIVE };
DeviceState currentState = STATE_IDLE;

unsigned long warningStartTime = 0;
const int WARNING_DURATION = 2000;

// Hardware timer to turn the screen off (10 seconds)
unsigned long alertStartTime = 0;
const int ALERT_DISPLAY_DURATION = 10000;

String currentApp = "";
String currentSender = "";
String currentSubject = "";
String currentSnippet = "";

void setup() {
AtomS3.begin(true);
// Flips the screen upside down depending on how you mount it.
// Change '2' to '0' if your screen text is upside down!
AtomS3.Display.setRotation(2);
AtomS3.Display.setTextDatum(middle_center);
setup_wifi();
drawOnlineScreen();
delay(1500);
client.setServer(mqtt_server, 1883);
client.setCallback(mqtt_callback);
// Turns screen off until first email arrives
drawIdleScreen();
}

void loop() {
AtomS3.update();
if (!client.connected()) { reconnect_mqtt(); }
client.loop();

switch (currentState) {
case STATE_IDLE: break;

case STATE_WARNING_ANIMATION:
if (millis() - warningStartTime >= WARNING_DURATION) {
currentState = STATE_ALERT_ACTIVE;
drawAlertDetails();
alertStartTime = millis(); // Start the 10-second countdown!
}
break;

case STATE_ALERT_ACTIVE:
// If button is pressed OR 10 seconds have passed, turn screen off
if (AtomS3.BtnA.wasPressed() || (millis() - alertStartTime >= ALERT_DISPLAY_DURATION)) {
currentState = STATE_IDLE;
drawIdleScreen();
}
break;
}
}

// --- 3. MQTT CALLBACK ---
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String incomingJson = "";
for (int i = 0; i < length; i++) incomingJson += (char)payload[i];

StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, incomingJson);

if (!error) {
currentApp = doc["app"].as<String>();
currentSender = doc["sender"].as<String>();
currentSubject = doc["subject"].as<String>();
currentSnippet = doc["snippet"].as<String>();

currentState = STATE_WARNING_ANIMATION;
warningStartTime = millis();
drawHazardWarning(); // This turns the screen back on
}
}

// --- NETWORK HELPERS ---
void setup_wifi() {
AtomS3.Display.setBrightness(200);
AtomS3.Display.fillScreen(TFT_BLACK);
AtomS3.Display.setTextColor(0x07B0);
AtomS3.Display.setTextSize(2);
AtomS3.Display.drawString("Wi-Fi...", 64, 64);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); }
}

void reconnect_mqtt() {
while (!client.connected()) {
String clientId = "AtomS3Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
client.subscribe(mqtt_topic);
} else {
delay(5000);
}
}
}

// --- ONLINE SCREEN ---
void drawOnlineScreen() {
AtomS3.Display.setBrightness(220);
AtomS3.Display.fillScreen(TFT_BLACK);

// Border frame
AtomS3.Display.drawRect(4, 4, 120, 120, 0x07B0);
AtomS3.Display.drawRect(6, 6, 116, 116, 0x0410);

// Corner accents
AtomS3.Display.fillRect( 4, 4, 8, 2, 0x07FF);
AtomS3.Display.fillRect( 4, 4, 2, 8, 0x07FF);
AtomS3.Display.fillRect(116, 4, 8, 2, 0x07FF);
AtomS3.Display.fillRect(122, 4, 2, 8, 0x07FF);
AtomS3.Display.fillRect( 4, 122, 8, 2, 0x07FF);
AtomS3.Display.fillRect( 4, 116, 2, 8, 0x07FF);
AtomS3.Display.fillRect(116, 122, 8, 2, 0x07FF);
AtomS3.Display.fillRect(122, 116, 2, 8, 0x07FF);

// Glowing status dot
AtomS3.Display.fillCircle(64, 44, 8, 0x07FF);
AtomS3.Display.fillCircle(64, 44, 5, 0x0330);

// ONLINE! text
AtomS3.Display.setTextDatum(middle_center);
AtomS3.Display.setTextColor(0x07FF);
AtomS3.Display.setTextSize(2);
AtomS3.Display.drawString("ONLINE!", 64, 72);

AtomS3.Display.setTextColor(0x07B0);
AtomS3.Display.setTextSize(1);
AtomS3.Display.drawString("CONNECTED", 64, 90);
}

// --- IDLE SCREEN (TURNS SCREEN COMPLETELY OFF) ---
void drawIdleScreen() {
AtomS3.Display.setBrightness(0); // Cuts the backlight power
AtomS3.Display.fillScreen(TFT_BLACK);
}

// --- HAZARD WARNING SCREEN ---
void drawHazardWarning() {
AtomS3.Display.setBrightness(255); // Powers the backlight back on!
AtomS3.Display.fillScreen(TFT_RED);
drawStripedBanner(0, 25);
drawStripedBanner(AtomS3.Display.height() - 25, 25);
AtomS3.Display.setTextColor(TFT_BLACK);
AtomS3.Display.setTextSize(3);
AtomS3.Display.drawString("ALERT", 64, 64);
}

void drawStripedBanner(int yOffset, int bannerHeight) {
if (yOffset == 0) {
AtomS3.Display.drawFastHLine(0, bannerHeight, 128, TFT_BLACK);
} else {
AtomS3.Display.drawFastHLine(0, yOffset - 1, 128, TFT_BLACK);
}
for (int x = -bannerHeight; x < 128; x += 30) {
for (int i = 0; i < 12; i++) {
AtomS3.Display.drawLine(x + i, yOffset, x + bannerHeight + i, yOffset + bannerHeight, TFT_BLACK);
}
}
}

// --- HELPERS ---
String truncateToFit(String text, int maxPx) {
while (text.length() > 1 && AtomS3.Display.textWidth(text) > maxPx)
text = text.substring(0, text.length() - 1);
return text;
}

String getFirstNWords(String text, int n) {
String result = "";
int count = 0, start = 0;
while (start < (int)text.length() && text[start] == ' ') start++;
for (int i = start; i <= (int)text.length(); i++) {
if (i == (int)text.length() || text[i] == ' ') {
if (i > start) {
if (count > 0) result += " ";
result += text.substring(start, i);
count++;
if (count >= n) break;
}
start = i + 1;
}
}
return result;
}

// --- ALERT DETAILS SCREEN ---
void drawAlertDetails() {
AtomS3.Display.fillScreen(TFT_BLACK);
AtomS3.Display.setBrightness(255);

// App name — big red bar
AtomS3.Display.fillRect(0, 0, 128, 30, TFT_RED);
AtomS3.Display.setTextDatum(middle_center);
AtomS3.Display.setTextColor(TFT_WHITE);
AtomS3.Display.setTextSize(2);
AtomS3.Display.drawString(currentApp, 64, 15);

// Sender
AtomS3.Display.setTextColor(TFT_YELLOW);
AtomS3.Display.setTextSize(1);
AtomS3.Display.drawString(truncateToFit(currentSender, 124), 64, 40);

AtomS3.Display.drawFastHLine(4, 50, 120, TFT_DARKGREY);

// Subject word-wrapped
AtomS3.Display.setTextDatum(top_center);
AtomS3.Display.setTextColor(TFT_WHITE);
AtomS3.Display.setTextSize(1);
String subject = currentSubject + " ";
int lineH = 12, y = 55, yMax = 90;
String line = "";
int sp = subject.indexOf(' ');
while (sp != -1) {
String word = subject.substring(0, sp + 1);
subject = subject.substring(sp + 1);
if (AtomS3.Display.textWidth(line + word) < 124) {
line += word;
} else {
if (y + lineH <= yMax) {
if (y + lineH * 2 > yMax && subject.length() > 0)
AtomS3.Display.drawString(truncateToFit(line + "...", 124), 64, y);
else
AtomS3.Display.drawString(line, 64, y);
y += lineH;
line = word;
} else break;
}
sp = subject.indexOf(' ');
}
if (line.length() > 0 && y + lineH <= yMax)
AtomS3.Display.drawString(line, 64, y);

AtomS3.Display.drawFastHLine(4, 96, 120, TFT_DARKGREY);

// Snippet preview
if (currentSnippet != "null" && currentSnippet != "") {
AtomS3.Display.setTextColor(0x7BEF);
AtomS3.Display.setTextSize(1);
AtomS3.Display.drawString(getFirstNWords(currentSnippet, 4) + "...", 64, 107);
}

AtomS3.Display.setTextDatum(middle_center);
}

Click the Upload arrow after choosing the M5AtomS3 board from the top dropdown menu after entering your credentials!

Assembly

1.gif
2.gif
IMG_20260412_221617.jpg
IMG_20260412_215050.jpg

It's time to put everything into a modern, 3D-printed body now that the cloud is listening and the brain has been programmed. The AtomS3 looks flawless on your desk thanks to this unique enclosure that conceals the battery and gives it a polished appearance.

  1. Get your 3D-printed base ready for the enclosure. To ensure that the battery and board are perfectly flat, remove any remaining stringing or support material from the printer right away.
  2. Seat the LiPo Battery: Gently insert your 3.7-volt LiPo battery into the 3D-printed case's lower portion. Maker advice: Handle LiPo batteries with care! Never squeeze or pierce them. Check your print again if it seems too tight.
  3. Route the Wires: To prevent pinching, coil the battery wires neatly. Securely insert the battery connector into the AtomS3's power port on the back or bottom.
  4. Install the AtomS3: Insert the AtomS3 into the upper portion of the housing that was 3D printed. Verify that the screen is oriented correctly (keep in mind that we turned the screen upside down in the code in Step 3, so position your board appropriately!).
  5. Get Rid of It: Align the 3D-printed case's upper and lower halves. Tighten your screws after inserting them into the mounting holes. Avoid overtightening to avoid stripping the 3D-printed plastic!

You have a fully wireless, self-contained, intelligent email pager once those screws are in place. Put it on your workbench, beneath your monitor, or anywhere else you need to maintain focus without missing anything crucial.

The Final Test Run

4.gif
5.gif
Timeline-1.gif

Here's what you should see after the code has finished uploading:

  1. When the AtomS3 boots up, a cool blue/green cyber-looking "ONLINE! CONNECTED" screen appears after the screen says "Wi-Fi..."
  2. Going Dark: The screen will turn entirely black after roughly 1.5 seconds. Remain calm! It entered stealth mode instead of breaking. It is currently waiting for a signal in silence.
  3. To initiate the trap, send an email from a VIP account that you have configured in your Gmail filter. Alternatively, you can manually initiate your Make.com scenario.
  4. The Alert: Your AtomS3 will flash a red hazard warning and display the sender's name and the email subject line in a matter of milliseconds.
  5. The Reset: You have two options for clearing the alert: either ignore it or press the physical screen, which functions as a huge button. The gadget will automatically turn off the backlight and return to sleep after ten seconds.

Conclusion

VID-20260413-WA0011.gif
IMG_20260413_115744.jpg
IMG_20260413_112742_Bokeh.jpg
IMG_20260413_113146.jpg

You've created a hardware solution to a contemporary digital issue by fusing a small amount of code, some astute cloud automation, and the compact but potent M5Stack AtomS3.

This build doesn't need to sit on a desk because it is fully wireless and runs on a LiPo battery. Put it in your pocket, fasten it to your lanyard, or clip it to the strap of your backpack! Now, you can go out and about with your phone securely tucked away. You can rest easy knowing that your AtomS3 will quickly exit its stealth mode to notify you if your supervisor, a major client, or a family member needs you. Everything else remains stuck in the cloud, where it belongs, including spam, newsletters, and sporadic app notifications.

Create Your Own! This build's greatest feature is how adaptable it is. With the framework in place, you can modify the hardware and code to completely customize it:

  1. Wearable Upgrades: You can add a velcro strap for your bag, a belt clip, or a carabiner loop to the 3D-printed case!
  2. Change the code: To make the screen flash red for work and yellow for family.
  3. Different Triggers: Use Make.com to set off the pager in response to other events, such as the beginning of a calendar event, a particular Slack message, or a severe weather warning prior to your commute.
  4. Longer Battery: To extend the LiPo battery's lifespan while you are away, adjust the deep-sleep parameters in the Arduino code.

Have fun creating and relishing your newfound focus!