Build a daily check-in button for a grandparent who lives alone

One big button, pressed once every morning, tells the family "I'm fine today". If nobody has pressed it by mid-morning, the button reminds them, and then the family's phones get a gentle message to call. Holding the button sends a...
  • Level:Intermediate
  • Duration: 1 h 15 min
  • Ages 12+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a daily check-in button for a grandparent who lives alone – EEMENTOR STEM™ lesson

Many older people live alone and are proud to be independent. Their families still worry: what if they fell, or felt ill, and nobody noticed for a day? Phoning every morning is one answer. This project is another, and it was designed together with that idea of independence: one big button that your grandparent presses once a day, for example after breakfast. The family's phones get a quiet "checked in" message. If the button hasn't been pressed by 10:00, it plays a friendly reminder tune. At 10:30, if there is still no press, the family gets a message: "No check-in yet, please give them a call." And if they ever need help, they hold the button for three seconds.

What you'll learn

  • How the ESP32 gets the real time from the internet (NTP), with your time zone
  • How to run a daily schedule and reset it at midnight
  • How a single button can do two things: a short press and a long press
  • How to queue messages so they are sent when Wi-Fi comes back

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • 1 push button, as big as you have (ESP32 IoT Starter Kit)
  • Green LED, red LED and 2 resistors, 220 Ω (ESP32 IoT Starter Kit)
  • Passive buzzer (ESP32 IoT Starter Kit)
  • Phones with the free ntfy app for the family
  • A USB phone charger, so it can stay on all the time
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Do it together, and with their agreement. A check-in button only works if the person wants it and understands what it sends. Set it up with them, agree on the times, and let them decide who gets the messages. It is not an emergency service: in an emergency, always call the emergency number.

Wiring

Breadboard wiring diagram: Build a daily check-in button for a grandparent who lives alone
Breadboard view. Check each connection against the wiring table below.
From To
Push button, one leg ESP32 GPIO 27
Push button, the diagonally opposite leg GND
ESP32 GPIO 25 220 Ω resistor, then the green LED's long leg
ESP32 GPIO 26 220 Ω resistor, then the red LED's long leg
LEDs' short legs GND
Passive buzzer + ESP32 GPIO 33
Passive buzzer − GND

Set up ntfy

The phone messages go through ntfy, a free notification service that needs no account. The ntfy lesson explains it step by step.

  1. Install the ntfy app from Google Play or the App Store.
  2. Make up a topic name that nobody could guess, such as eementor-family-4k8p2x. Use only letters, numbers, - and _.
  3. In the app, tap +, type your topic name and tap Subscribe. Everyone who should get the messages subscribes to the same topic on their own phone.
  4. Test it: open https://ntfy.sh/your-topic-name in a browser, type a message and send it. It should pop up on the phones.

Important: on the free ntfy.sh server, anyone who knows your topic name can read your messages. Keep the name long and random, and never send personal details through it.

The sketch

Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only).

Change TIME_ZONE to your own time zone. The example is for Central Europe. Search for "POSIX time zone" and your city to find yours, for example EET-2EEST,M3.5.0/3,M10.5.0/4 for Estonia, GMT0BST,M3.5.0/1,M10.5.0 for the UK, <+04>-4 for the UAE and Oman, or <+03>-3 for Saudi Arabia, Qatar, Kuwait and Bahrain.

Put your topic in TOPIC and the person's name in NAME. Change the three times if they get up earlier or later. Everything else is built into the ESP32 board package.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Preferences.h>
#include <time.h>

const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* TOPIC = "YOUR_SECRET_TOPIC";      // for example eementor-family-4k8p2x
const char* NAME = "Grandma";                 // who presses the button
const char* TIME_ZONE = "CET-1CEST,M3.5.0,M10.5.0/3";

const int CHECKIN_FROM = 7 * 60;              // 07:00 the button starts asking (minutes after midnight)
const int REMIND_AT = 10 * 60;                // 10:00 a reminder tune if not pressed yet
const int ALERT_AT = 10 * 60 + 30;            // 10:30 tell the family
const int NIGHT_FROM = 21 * 60;               // 21:00 lights off for the night

const int BUTTON_PIN = 27, GREEN_PIN = 25, RED_PIN = 26, BUZZER_PIN = 33;

Preferences prefs;
int today = -1;
bool checkedIn = false, alerted = false;
unsigned long lastReminder = 0, pressStart = 0, helpUntil = 0, lastWifiTry = 0, lastSendTry = 0;
bool lastButton = HIGH, longDone = false;

// Up to three messages wait here until they have been sent
struct Message { bool waiting; String title, text, priority, tags; };
Message outbox[3];
enum { CHECKIN, MISSED, HELP };

void queue(int slot, String title, String text, String priority, String tags) {
  outbox[slot] = {true, title, text, priority, tags};
  lastSendTry = 0;                            // try straight away
}

bool send(Message& m) {
  WiFiClientSecure client;
  client.setInsecure();
  HTTPClient http;
  http.setTimeout(5000);
  http.begin(client, String("https://ntfy.sh/") + TOPIC);
  http.addHeader("Title", m.title);
  http.addHeader("Priority", m.priority);
  http.addHeader("Tags", m.tags);
  int code = http.POST(m.text);
  http.end();
  Serial.printf("ntfy answered %d\n", code);
  return code == 200;
}

void melody(const int* notes, int n) {
  for (int i = 0; i < n; i++) {
    tone(BUZZER_PIN, notes[i], 160);
    delay(200);
  }
}
const int HAPPY[] = {523, 659, 784, 1047};
const int REMINDER[] = {784, 659, 784, 659};

String clockText(struct tm& t) {
  char buf[6];
  strftime(buf, sizeof(buf), "%H:%M", &t);
  return String(buf);
}

void shortPress(bool timeOk, struct tm& t) {
  if ((long)(helpUntil - millis()) > 0) {     // pressed again after asking for help
    helpUntil = 0;
    queue(HELP, String(NAME) + " is OK now", String(NAME) + " pressed the button again: they are OK now.", "high", "white_check_mark");
    melody(HAPPY, 4);
    return;
  }
  if (checkedIn) {                            // already done today: just a little "yes"
    tone(BUZZER_PIN, 1047, 100);
    return;
  }
  checkedIn = true;
  if (timeOk) prefs.putInt("day", t.tm_yday); // remembered even after a power cut
  melody(HAPPY, 4);
  String when = timeOk ? " at " + clockText(t) : "";
  String late = alerted ? " (later than usual)" : "";
  queue(CHECKIN, String(NAME) + " checked in", String(NAME) + " pressed the button" + when + late + ". All is well.", "low", "white_check_mark");
  Serial.println("Checked in");
}

void askForHelp() {
  helpUntil = millis() + 600000;              // red light blinks for 10 minutes
  queue(HELP, String(NAME) + " needs help", String(NAME) + " held the help button. Please call them now, or go and check.", "urgent", "sos");
  for (int i = 0; i < 3; i++) {
    tone(BUZZER_PIN, 440, 150);
    delay(250);
  }
  Serial.println("Help requested");
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(RED_PIN, OUTPUT);
  prefs.begin("checkin", false);
  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  configTzTime(TIME_ZONE, "pool.ntp.org", "time.google.com");
  Serial.println("Check-in button starting");
}

void loop() {
  unsigned long now = millis();
  struct tm t;
  bool timeOk = getLocalTime(&t, 10);
  int minutes = timeOk ? t.tm_hour * 60 + t.tm_min : -1;

  // A new day: start again (unless the button was already pressed today before a restart)
  if (timeOk && t.tm_yday != today) {
    today = t.tm_yday;
    checkedIn = prefs.getInt("day", -1) == today;
    alerted = false;
    lastReminder = 0;
    Serial.printf("Today is day %d, checked in: %s\n", today, checkedIn ? "yes" : "no");
  }

  // Button: short press = check in, hold 3 s = help
  bool button = digitalRead(BUTTON_PIN);
  if (lastButton == HIGH && button == LOW) {
    pressStart = now;
    longDone = false;
  }
  if (button == LOW && !longDone && now - pressStart > 3000) {
    longDone = true;
    askForHelp();
  }
  if (lastButton == LOW && button == HIGH && !longDone && now - pressStart > 50) shortPress(timeOk, t);
  lastButton = button;

  // The daily schedule
  if (timeOk && !checkedIn && minutes >= REMIND_AT && minutes < NIGHT_FROM && (lastReminder == 0 || now - lastReminder > 600000)) {
    lastReminder = now;                       // a friendly tune every 10 minutes
    melody(REMINDER, 4);
  }
  if (timeOk && !checkedIn && !alerted && minutes >= ALERT_AT) {
    alerted = true;
    queue(MISSED, "No check-in yet", "No check-in from " + String(NAME) + " yet today. Please give them a call.", "high", "warning");
  }

  // Lights
  bool helpOn = (long)(helpUntil - now) > 0;
  bool day = timeOk && minutes >= CHECKIN_FROM && minutes < NIGHT_FROM;
  if (helpOn) digitalWrite(RED_PIN, (now / 200) % 2);
  else digitalWrite(RED_PIN, !timeOk && (now / 1000) % 2);   // slow red: no time yet, check the Wi-Fi
  digitalWrite(GREEN_PIN, day && (checkedIn || (now / 1000) % 2));

  // Wi-Fi and the outbox
  if (WiFi.status() != WL_CONNECTED && now - lastWifiTry > 30000) {
    lastWifiTry = now;
    WiFi.disconnect();
    WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  }
  if (WiFi.status() == WL_CONNECTED && (lastSendTry == 0 || now - lastSendTry > 30000)) {
    for (int i = 0; i < 3; i++) {
      if (!outbox[i].waiting) continue;
      lastSendTry = now;
      if (send(outbox[i])) outbox[i].waiting = false;
      else break;                             // no luck: try again in 30 s
    }
  }
}

Try it

  1. For testing, set the three times a few minutes from now, for example CHECKIN_FROM = 15 * 60 + 2 (15:02), REMIND_AT = 15 * 60 + 4 and ALERT_AT = 15 * 60 + 5.
  2. Upload the sketch and open the Serial Monitor at 115200 baud. A slowly blinking red LED means it doesn't know the time yet; it stops once Wi-Fi and the time are there.
  3. At the check-in time, the green LED starts blinking: "press me". Don't press it yet. At the reminder time the buzzer plays a tune, and at the alert time the phones get "No check-in yet".
  4. Now press the button briefly: a happy tune, the green LED stays on, and the phones get "checked in (later than usual)".
  5. Hold the button for 3 seconds: three low beeps, the red LED blinks and the phones get an urgent "needs help" message. Press briefly to send "is OK now". Put the real times back before you give it away.

How it works

The ESP32 has no battery-backed clock, so it asks a time server on the internet. configTzTime() starts that with NTP (Network Time Protocol) and sets your time zone, including summer time. From then on getLocalTime() fills a tm structure with the hour, the minute and tm_yday, the day of the year. The sketch turns the time into "minutes after midnight", so 10:30 is simply 630, and comparing times becomes comparing numbers.

When tm_yday changes, a new day has started and the check-in resets. The day of the last check-in is also saved with Preferences in the ESP32's flash memory. Without it, a power cut at noon would make the button forget the morning press and wrongly tell the family that nobody checked in.

Messages are never sent straight from the button code. They go into a small outbox first, and the end of loop() sends whatever is waiting. If the Wi-Fi is down, the message stays in the outbox and is tried again every 30 seconds. Important messages like "needs help" should never be lost just because the router was restarting.

client.setInsecure() keeps the connection encrypted but skips checking the server's certificate. That keeps the sketch short and is fine for a learning project. For a device you leave running for years, load the server's root certificate with client.setCACert() instead.

Try this next

  • Send a daily "the button is working" message at noon, so the family notices if it has lost power or Wi-Fi.
  • Use a big arcade button or put the button in a box with a large, soft lid.
  • Add a second check-in in the evening.
  • Add the PIR motion sensor: if it saw movement in the morning, send "probably fine, but didn't press the button" instead.

Troubleshooting

  • The red LED blinks slowly and nothing happens: it has no time yet. Check the Wi-Fi name and password and that the network has internet access.
  • The times are an hour or more off: the time zone is wrong. Check TIME_ZONE.
  • A short press sends "needs help": the press was longer than 3 seconds. Press and release quickly.
  • No message on the phone: look for "ntfy answered" in the Serial Monitor. 200 means it was sent: check the topic name in the app letter by letter. -1 means no internet; 429 means too many messages in a short time.
  • It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit