In India, power outages are frequent. Sometimes they’re short interruptions, other times they last for hours. I wanted a way to detect exactly when the grid goes down and when it comes back, so I could act immediately and keep a record.
The system I built uses a Raspberry Pi (running on backup power) and a XiaoESP32-C3 (powered directly from the grid). The Pi pings the ESP32, and if it stops responding, I know the power is out. The Pi then sends me an instant Telegram message.
Note: Full codebase Git repo is attached at the end of this post.
Hardware SetupYou’ll need:
- Raspberry Pi (connected to backup power/UPS) with OS installed - Tested with Ubuntu Server
- XiaoESP32-C3 (connected to grid outlet only)
- WiFi network both devices can access (ensure router is on backup power/UPS)
- USB cable to flash ESP32
Wiring concept:
- ESP32 is powered directly from the grid.
- When grid power fails, ESP32 shuts off.
- Raspberry Pi runs continuously on backup and pings ESP32.
- If ESP32 doesn’t respond, Pi marks it as outage and sends alert.
The ESP32 just needs to connect to WiFi. No advanced logic is required—it only needs to be reachable.
// XIAO ESP32C3 Wi-Fi diagnose + connect
// Arduino-ESP32 v2/v3 compatible
#include <WiFi.h>
const char* SSID = "WIFI_NAME";
const char* PASS = "WIFI_PASSWORD";
// v3: two-arg event handler gives reason codes on disconnect
void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
Serial.println("[WiFi] Associated with AP");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.print("[WiFi] IP: ");
Serial.println(WiFi.localIP());
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.printf("[WiFi] Disconnected. reason=%d\n", info.wifi_sta_disconnected.reason);
WiFi.reconnect();
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("Scanning for WiFi networks...");
int n = WiFi.scanNetworks();
if (n == 0) {
Serial.println("No networks found.");
} else {
Serial.printf("%d networks found:\n", n);
for (int i = 0; i < n; ++i) {
Serial.printf("%2d: %s (%d dBm) %s\n",
i + 1,
WiFi.SSID(i).c_str(),
WiFi.RSSI(i),
(WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "open" : "secured"
);
delay(10);
}
}
WiFi.persistent(false);
WiFi.setSleep(false); // keep radio awake
WiFi.mode(WIFI_STA);
WiFi.setHostname("xiao-esp32c3"); // optional // set your country
WiFi.onEvent(onWiFiEvent);
WiFi.setAutoReconnect(true);
Serial.printf("[WiFi] Connecting to %s\n", SSID);
WiFi.begin(SSID, PASS);
// wait once for first link
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 20000) {
delay(250);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("[WiFi] Connected. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.printf("[WiFi] Initial connect failed. status=%d\n", WiFi.status());
}
}
void loop() {
// keep alive check; reconnect handled by event + autoReconnect
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] Not connected. Nudge reconnect…");
WiFi.reconnect();
}
delay(5000);
Step 2: Raspberry Pi Ping ScriptThe Raspberry Pi is the “source of truth.” It runs a script that pings the ESP32 at intervals.
Note: It also needs an env file, located at /etc/grid_online_check_ping_env
#!/usr/bin/env bash
# /usr/local/bin/check_ping_telegram.sh
# Ping TARGET and send telegram alert once per outage. Recovery on return.
# Requires: curl, ping, logger
# Ensure /etc/grid_online_check_ping_env exists with BOT_TOKEN, CHAT_ID, TARGET
ENV_FILE="/etc/grid_online_check_ping_env"
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
set -eo pipefail
# safe defaults if not set in env
ARG1="${1:-}"
TARGET="${TARGET:-$ARG1}"
BOT_TOKEN="${BOT_TOKEN:-}"
CHAT_ID="${CHAT_ID:-}"
RETRIES="${RETRIES:-3}"
PING_TIMEOUT="${PING_TIMEOUT:-2}"
STATE_DIR="${STATE_DIR:-/var/tmp}"
LOCKFILE="/var/lock/check_ping_telegram.lock"
if [ -z "$TARGET" ]; then
logger -t check_ping "ERROR: TARGET not set. Provide in ${ENV_FILE} or as first arg."
echo "ERROR: TARGET not set. Provide in ${ENV_FILE} or as first arg." >&2
exit 2
fi
if [ -z "$BOT_TOKEN" ] || [ -z "$CHAT_ID" ]; then
logger -t check_ping "ERROR: BOT_TOKEN or CHAT_ID missing in ${ENV_FILE}."
echo "ERROR: BOT_TOKEN or CHAT_ID missing in ${ENV_FILE}." >&2
exit 2
fi
mkdir -p "$STATE_DIR"
STATE_FILE="$STATE_DIR/check_ping_${TARGET//[^a-zA-Z0-9_.-]/_}.state"
timestamp() { date -u +"%Y-%m-%d %H:%M:%SZ"; }
# prevent concurrent runs
exec 9>"$LOCKFILE"
if ! flock -n 9; then
logger -t check_ping "Another instance running. Exiting."
exit 0
fi
# try pings
success=0
for i in $(seq 1 "$RETRIES"); do
if ping -c 1 -W "$PING_TIMEOUT" "$TARGET" >/dev/null 2>&1; then
success=1
break
fi
sleep 0.2
done
prev_state=""
if [ -f "$STATE_FILE" ]; then
prev_state=$(cat "$STATE_FILE" 2>/dev/null || echo "")
fi
if [ $success -eq 1 ]; then
if [ "$prev_state" = "ALERTED" ]; then
local_text="<b>Recovery:</b> Grid is online again."
logger -t check_ping "$local_tex
fi
rm -f "$STATE_FILE" || true
else
if [ "$prev_state" != "ALERTED" ]; then
local_text="<b>Alert:</b> Grid is offline."
logger -t check_ping "$local_text"
printf 'ALERTED\n' > "$STATE_FILE"
sync
else
logger -t check_ping "Still down: ${TARGET} (alert already sent)."
fi
fi
# flock released on exit
exit 0
Step 3: Setup Telegram Bot1. Open Telegram, search for BotFather.
2. Run `/newbot` and follow prompts to get a bot token.
3. Get your CHAT_ID via opening telegram in web browser
Step 4: Send Notifications via PiExtend the Pi script to send Telegram messages:
#!/usr/bin/env bash
# /usr/local/bin/check_ping_telegram.sh
# Ping TARGET and send telegram alert once per outage. Recovery on return.
# Requires: curl, ping, logger
# Ensure /etc/grid_online_check_ping_env exists with BOT_TOKEN, CHAT_ID, TARGET
ENV_FILE="/etc/grid_online_check_ping_env"
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
set -eo pipefail
# safe defaults if not set in env
ARG1="${1:-}"
TARGET="${TARGET:-$ARG1}"
BOT_TOKEN="${BOT_TOKEN:-}"
CHAT_ID="${CHAT_ID:-}"
RETRIES="${RETRIES:-3}"
PING_TIMEOUT="${PING_TIMEOUT:-2}"
STATE_DIR="${STATE_DIR:-/var/tmp}"
LOCKFILE="/var/lock/check_ping_telegram.lock"
if [ -z "$TARGET" ]; then
logger -t check_ping "ERROR: TARGET not set. Provide in ${ENV_FILE} or as first arg."
echo "ERROR: TARGET not set. Provide in ${ENV_FILE} or as first arg." >&2
exit 2
fi
if [ -z "$BOT_TOKEN" ] || [ -z "$CHAT_ID" ]; then
logger -t check_ping "ERROR: BOT_TOKEN or CHAT_ID missing in ${ENV_FILE}."
echo "ERROR: BOT_TOKEN or CHAT_ID missing in ${ENV_FILE}." >&2
exit 2
fi
mkdir -p "$STATE_DIR"
STATE_FILE="$STATE_DIR/check_ping_${TARGET//[^a-zA-Z0-9_.-]/_}.state"
send_telegram() {
local text="$1"
curl -sS --max-time 10 --fail -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${CHAT_ID}" \
--data-urlencode "text=${text}" \
--data "parse_mode=HTML" >/dev/null 2>&1 || true
}
timestamp() { date -u +"%Y-%m-%d %H:%M:%SZ"; }
# prevent concurrent runs
exec 9>"$LOCKFILE"
if ! flock -n 9; then
logger -t check_ping "Another instance running. Exiting."
exit 0
fi
# try pings
success=0
for i in $(seq 1 "$RETRIES"); do
if ping -c 1 -W "$PING_TIMEOUT" "$TARGET" >/dev/null 2>&1; then
success=1
break
fi
sleep 0.2
done
prev_state=""
if [ -f "$STATE_FILE" ]; then
prev_state=$(cat "$STATE_FILE" 2>/dev/null || echo "")
fi
if [ $success -eq 1 ]; then
if [ "$prev_state" = "ALERTED" ]; then
local_text="<b>Recovery:</b> Grid is online again."
logger -t check_ping "$local_text"
send_telegram "$local_text"
fi
rm -f "$STATE_FILE" || true
else
if [ "$prev_state" != "ALERTED" ]; then
local_text="<b>Alert:</b> Grid is offline."
logger -t check_ping "$local_text"
send_telegram "$local_text"
printf 'ALERTED\n' > "$STATE_FILE"
sync
else
logger -t check_ping "Still down: ${TARGET} (alert already sent)."
fi
fi
# flock released on exit
exit 0
Step 5: Add this Pi script to cronjob in Pi# give execute permission to the script (use sudo if needed)
$ chmod +x /usr/local/bin/check_ping_telegram.sh
$ crontab -e
add the below line which will trigger this script every 1 minute
*/1 * * * * /usr/local/bin/check_ping_telegram.sh
Step 6: End-to-End Flow1. Grid ON → ESP32 powered → Pi pings successfully → “grid online.”
2. Grid OFF → ESP32 off → Pi pings fail → “⚡ outage detected.”
3. Grid returns → ESP32 back online → Pi pings succeed → “✅ power restored.”
Future Work- Store outage logs in a database for long-term analysis.
- Build dashboards for visualization.
- Add multiple ESP32 nodes in different circuits for granular tracking.
- Setup additional alert to notify if outage has lasted for more than 1hr, so as to raise complaint with the power corporation.
Comments