Lightning Tracker
I'm thrilled to introduce my latest project: a real-time Lightning Tracker for North America! By leveraging Blitzortung’s powerful WebSocket API, I've developed a system that continuously monitors lightning activity across the continent. Here's an overview of how it works:
- Data Acquisition: A script runs smoothly on an Amazon EC2 instance, connecting to Blitzortung’s WebSocket to receive live data on lightning strikes.
- Intelligent Processing: The system decodes the incoming information and filters it to focus exclusively on events occurring within North America.
- Instant Notifications: When a lightning strike is detected in the specified region, the script sends the keyword “lightning” via WebSocket to my ESP device, enabling real-time alerts.
Supplies
- ESP32-WROOM
- LEDs
- Resistors
- Jumper Cables
WiFi Status LED
- Pin Connection: Connect the WiFi status LED to Pin 14 on the ESP device.
- Resistor: Use a resistor in series to limit the current and protect the LED.
WebSocket Status LED:
- Pin Connection: Connect the WebSocket status LED to Pin 16 on the ESP device.
- Resistor: Use a resistor in series to limit the current and protect the LED.
Flexible LED Filaments:
- Pin Connections: Connect each of the three flexible LED filaments to Pins 18, 19, and 22 respectively.
- Resistors: Each filament should have its own resistor to ensure proper current flow.
- Functionality: These LEDs will flash simultaneously whenever a lightning strike is detected, providing a visual representation of storm activity.
Upload Code to ESP32
Upload the code to the ESP32. Changing Wi-Fi information and server information for your usage.
#include <WebSocketsClient.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <pgmspace.h>
// Wi-Fi credentials
const char* ssid = "CHANGEME";
const char* password = "CHANGEME";
// WebSocket server details
const char* websocket_host = "CHANGEME";
const uint16_t websocket_port = CHANGEME;
const char* websocket_path = "/";
// LED pin definitions
const int flashPins[] = {18, 19, 22};
const int numFlashPins = sizeof(flashPins) / sizeof(flashPins[0]);
const int wifiLedPin = 14;
const int wsLedPin = 16;
WebSocketsClient webSocket;
// Flashing mechanism variables
volatile int flashQueue = 0;
const int maxFlashQueue = 10;
unsigned long lastFlashTime = 0;
bool isFlashing = false;
const unsigned long flashDuration = 100;
const unsigned long flashInterval = 150;
// Reconnection variables
unsigned long lastReconnectAttempt = 0;
const unsigned long reconnectInterval = 5000;
// Heap monitoring variables
unsigned long lastHeapCheck = 0;
const unsigned long heapCheckInterval = 60000;
// Wi-Fi reconnection variables
unsigned long lastWifiCheck = 0;
const unsigned long wifiCheckInterval = 10000;
void webSocketEvent(WStype_t type, uint8_t* payload, size_t length);
void flashLEDs();
void connectWebSocket();
void reconnectWebSocket();
void monitorHeap();
void updateWifiStatusLED();
void updateWebSocketStatusLED();
void setup() {
// Initialize Serial for debugging
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("ESP32 WebSocket Client");
// Initialize LED pins
for (int i = 0; i < numFlashPins; i++) {
pinMode(flashPins[i], OUTPUT);
digitalWrite(flashPins[i], LOW);
}
pinMode(wifiLedPin, OUTPUT);
digitalWrite(wifiLedPin, LOW);
pinMode(wsLedPin, OUTPUT);
digitalWrite(wsLedPin, LOW);
// Connect to Wi-Fi
Serial.print("Connecting to Wi-Fi");
WiFi.begin(ssid, password);
int wifiRetryCount = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
wifiRetryCount++;
if (wifiRetryCount > 20) {
Serial.println("\nFailed to connect to WiFi. Rebooting...");
ESP.restart();
}
}
Serial.println();
Serial.print("Connected to Wi-Fi. IP Address: ");
Serial.println(WiFi.localIP());
// Turn on Wi-Fi status LED
digitalWrite(wifiLedPin, HIGH);
// Connect to WebSocket server
connectWebSocket();
}
void loop() {
// Handle WebSocket events
webSocket.loop();
unsigned long currentMillis = millis();
// Handle LED flashing
if (isFlashing) {
if (currentMillis - lastFlashTime >= flashDuration) {
// Turn off all flash LEDs
for (int i = 0; i < numFlashPins; i++) {
digitalWrite(flashPins[i], LOW);
}
isFlashing = false;
lastFlashTime = currentMillis;
}
} else {
if (flashQueue > 0 && (currentMillis - lastFlashTime >= flashInterval)) {
// Turn on all flash LEDs
for (int i = 0; i < numFlashPins; i++) {
digitalWrite(flashPins[i], HIGH);
}
isFlashing = true;
lastFlashTime = currentMillis;
flashQueue--;
}
}
// Monitor Wi-Fi connection
if (WiFi.status() != WL_CONNECTED) {
// Turn off Wi-Fi status LED
digitalWrite(wifiLedPin, LOW);
// Attempt to reconnect to Wi-Fi if interval has passed
if (currentMillis - lastWifiCheck > wifiCheckInterval) {
Serial.println("Wi-Fi disconnected. Attempting to reconnect...");
WiFi.disconnect();
WiFi.begin(ssid, password);
lastWifiCheck = currentMillis;
}
} else {
// Ensure Wi-Fi status LED is on
digitalWrite(wifiLedPin, HIGH);
}
// Attempt to reconnect to WebSocket if disconnected
if (!webSocket.isConnected()) {
if (currentMillis - lastReconnectAttempt > reconnectInterval) {
Serial.println("Attempting to reconnect to WebSocket...");
reconnectWebSocket();
lastReconnectAttempt = currentMillis;
}
}
// Monitor heap memory periodically
if (currentMillis - lastHeapCheck > heapCheckInterval) {
monitorHeap();
lastHeapCheck = currentMillis;
}
}
void flashLEDs() {
if (flashQueue < maxFlashQueue) {
flashQueue++;
} else {
Serial.println("Flash queue is full. Ignoring additional flash requests.");
}
}
void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
Serial.println("WebSocket Disconnected");
// Turn off WebSocket status LED
digitalWrite(wsLedPin, LOW);
break;
case WStype_CONNECTED:
Serial.println("WebSocket Connected");
// Turn on WebSocket status LED
digitalWrite(wsLedPin, HIGH);
webSocket.sendTXT("Hello Server!");
break;
case WStype_TEXT: {
String message = String((char*)payload).substring(0, length);
Serial.print("Received message: ");
Serial.println(message);
if (message.indexOf("lightning") != -1) {
flashLEDs();
Serial.println("LED flash queued due to 'lightning' message.");
}
} break;
case WStype_BIN:
Serial.println("Received binary data.");
break;
case WStype_ERROR:
Serial.println("WebSocket Error!");
break;
case WStype_PING:
case WStype_PONG:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
break;
}
}
void connectWebSocket() {
Serial.print("Connecting to WebSocket server: wss://");
Serial.print(websocket_host);
Serial.print(":");
Serial.print(websocket_port);
Serial.println(websocket_path);
// Initialize WebSocket connection with SSL
webSocket.beginSSL(websocket_host, websocket_port, websocket_path);
webSocket.onEvent(webSocketEvent);
}
void reconnectWebSocket() {
Serial.println("Reconnecting to WebSocket...");
webSocket.disconnect();
delay(1000);
connectWebSocket();
}
void monitorHeap() {
Serial.print("Free Heap: ");
Serial.println(ESP.getFreeHeap());
}
Upload and Run Python Script
Setting up a server to run this and point to a domain is a little out of the scope of this post.
import json
import asyncio
from websocket import create_connection, WebSocketException
from websockets import serve
def decode(b):
print("[DEBUG] Decoding started")
e = {}
d = list(b)
c = d[0]
f = c
g = [c]
h = 256
o = h
for i in range(1, len(d)):
a = ord(d[i])
a = d[i] if h > a else e.get(a, f + c)
g.append(a)
c = a[0]
e[o] = f + c
o += 1
f = a
print("[DEBUG] Decoding finished successfully")
return "".join(g)
connected_clients = set()
async def ws_server(websocket, path):
print("[INFO] New client connected")
connected_clients.add(websocket)
try:
async for message in websocket:
print(f"[INFO] Received message from client: {message}")
except Exception as e:
print(f"[ERROR] Client connection error: {e}")
finally:
connected_clients.remove(websocket)
print("[INFO] Client disconnected")
async def notify_clients():
if connected_clients:
message = "lightning"
print(f"[INFO] Broadcasting message to clients: {message}")
await asyncio.gather(
*[client.send(message) for client in connected_clients],
return_exceptions=True
)
async def upstream_listener():
servers = ["wss://ws1.blitzortung.org:443", "wss://ws8.blitzortung.org:443"]
initial_message = json.dumps({"a": 111})
for server_url in servers:
try:
print(f"[INFO] Connecting to upstream server: {server_url}")
ws = create_connection(server_url)
ws.send(initial_message)
print(f"[INFO] Connected to upstream server: {server_url}")
while True:
try:
encoded_response = ws.recv()
decoded_message = decode(encoded_response)
decoded_json = json.loads(decoded_message)
if decoded_json.get("region") == 3:
print("[INFO] Match found for region 3")
await notify_clients()
except Exception as e:
print(f"[ERROR] Error while processing upstream message: {e}")
break
except WebSocketException as e:
print(f"[ERROR] WebSocket connection error: {e}")
finally:
print("[INFO] Closing connection to upstream server")
ws.close()
async def main():
print("[INFO] Starting WebSocket server...")
ws_server_task = await serve(ws_server, "0.0.0.0", 8000)
upstream_task = asyncio.create_task(upstream_listener())
print("[INFO] WebSocket server running on port 8000")
await asyncio.gather(ws_server_task.wait_closed(), upstream_task)
if __name__ == "__main__":
asyncio.run(main())