Aahan Sharma
Published © MIT

NotiFY

An M5Stack-powered NotiFY basic notification alert accessory.

IntermediateFull instructions provided5 hours11
NotiFY

Things used in this project

Story

Read more

Custom parts and enclosures

Lower Black

lower part of the body 3d printed

Top

upper part of the body 3d printed

Schematics

Lipo Battery connection with atoms3

To connect lipo battery to the atoms3 safely

Code

Source Code

C/C++
It is Used In Arduino file is .ino
written 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);
}

Credits

Aahan Sharma
6 projects • 1 follower
As a student of computer science, I enjoy experimenting with tech-related things.

Comments