Lightning Tracker

by Emorrow in Circuits > Microcontrollers

37 Views, 0 Favorites, 0 Comments

Lightning Tracker

PXL_20250103_213425329.jpg
ESP32 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:

  1. Data Acquisition: A script runs smoothly on an Amazon EC2 instance, connecting to Blitzortung’s WebSocket to receive live data on lightning strikes.
  2. Intelligent Processing: The system decodes the incoming information and filters it to focus exclusively on events occurring within North America.
  3. 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

  1. ESP32-WROOM
  2. LEDs
  3. Resistors
  4. Jumper Cables

WiFi Status LED


  1. Pin Connection: Connect the WiFi status LED to Pin 14 on the ESP device.
  2. Resistor: Use a resistor in series to limit the current and protect the LED.

WebSocket Status LED:


  1. Pin Connection: Connect the WebSocket status LED to Pin 16 on the ESP device.
  2. Resistor: Use a resistor in series to limit the current and protect the LED.

Flexible LED Filaments:


  1. Pin Connections: Connect each of the three flexible LED filaments to Pins 18, 19, and 22 respectively.
  2. Resistors: Each filament should have its own resistor to ensure proper current flow.
  3. 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())