#include #include #include #include #include #include #include #include #include #include #include "esp_wifi.h" #include "esp_wifi_types.h" // ============================================================================ // CONFIGURATION // ============================================================================ // Hardware Configuration #define BUZZER_PIN 3 // GPIO3 (D2) - PWM capable pin on Xiao ESP32 S3 // Audio Configuration #define LOW_FREQ 200 // Boot sequence - low pitch #define HIGH_FREQ 800 // Boot sequence - high pitch & detection alert #define DETECT_FREQ 1000 // Detection alert - high pitch (faster beeps) #define HEARTBEAT_FREQ 600 // Heartbeat pulse frequency #define BOOT_BEEP_DURATION 300 // Boot beep duration #define DETECT_BEEP_DURATION 150 // Detection beep duration (faster) #define HEARTBEAT_DURATION 100 // Short heartbeat pulse // WiFi Promiscuous Mode Configuration #define MAX_CHANNEL 13 #define CHANNEL_HOP_INTERVAL 2500 // milliseconds // Detection Pattern Limits #define MAX_SSID_PATTERNS 10 #define MAX_MAC_PATTERNS 50 #define MAX_DEVICE_NAMES 20 // ============================================================================ // DETECTION PATTERNS (Extracted from Real Flock Safety Device Databases) // ============================================================================ // WiFi SSID patterns to detect (case-insensitive) static const char* wifi_ssid_patterns[] = { "flock", // Standard Flock Safety naming "Flock", // Capitalized variant "FLOCK", // All caps variant "FS Ext Battery", // Flock Safety Extended Battery devices "Penguin", // Penguin surveillance devices "Pigvision" // Pigvision surveillance systems }; // Known Flock Safety MAC address prefixes (from real device databases) static const char* mac_prefixes[] = { // FS Ext Battery devices "58:8e:81", "cc:cc:cc", "ec:1b:bd", "90:35:ea", "04:0d:84", "f0:82:c0", "1c:34:f1", "38:5b:44", "94:34:69", "b4:e3:f9", // Flock WiFi devices "70:c9:4e", "3c:91:80", "d8:f3:bc", "80:30:49", "14:5a:fc", "74:4c:a1", "08:3a:88", "9c:2f:9d", // Penguin devices "cc:09:24", "ed:c7:63", "e8:ce:56", "ea:0c:ea", "d8:8f:14", "f9:d9:c0", "f1:32:f9", "f6:a0:76", "e4:1c:9e", "e7:f2:43", "e2:71:33", "da:91:a9", "e1:0e:15", "c8:ae:87", "f4:ed:b2", "d8:bf:b5", "ee:8f:3c", "d7:2b:21", "ea:5a:98" }; // Device name patterns for BLE advertisement detection static const char* device_name_patterns[] = { "FS Ext Battery", // Flock Safety Extended Battery "Penguin", // Penguin surveillance devices "Flock", // Standard Flock Safety devices "Pigvision" // Pigvision surveillance systems }; // ============================================================================ // GLOBAL VARIABLES // ============================================================================ static uint8_t current_channel = 1; static unsigned long last_channel_hop = 0; static bool triggered = false; static bool device_in_range = false; static unsigned long last_detection_time = 0; static unsigned long last_heartbeat = 0; static NimBLEScan* pBLEScan; // ============================================================================ // AUDIO SYSTEM // ============================================================================ void beep(int frequency, int duration_ms) { tone(BUZZER_PIN, frequency, duration_ms); delay(duration_ms + 50); } void boot_beep_sequence() { printf("Initializing audio system...\n"); printf("Playing boot sequence: Low -> High pitch\n"); beep(LOW_FREQ, BOOT_BEEP_DURATION); beep(HIGH_FREQ, BOOT_BEEP_DURATION); printf("Audio system ready\n\n"); } void flock_detected_beep_sequence() { printf("FLOCK SAFETY DEVICE DETECTED!\n"); printf("Playing alert sequence: 3 fast high-pitch beeps\n"); for (int i = 0; i < 3; i++) { beep(DETECT_FREQ, DETECT_BEEP_DURATION); if (i < 2) delay(50); // Short gap between beeps } printf("Detection complete - device identified!\n\n"); // Mark device as in range and start heartbeat tracking device_in_range = true; last_detection_time = millis(); last_heartbeat = millis(); } void heartbeat_pulse() { printf("Heartbeat: Device still in range\n"); beep(HEARTBEAT_FREQ, HEARTBEAT_DURATION); delay(100); beep(HEARTBEAT_FREQ, HEARTBEAT_DURATION); } // ============================================================================ // JSON OUTPUT FUNCTIONS // ============================================================================ void output_wifi_detection_json(const char* ssid, const uint8_t* mac, int rssi, const char* detection_type) { DynamicJsonDocument doc(2048); // Core detection info doc["timestamp"] = millis(); doc["detection_time"] = String(millis() / 1000.0, 3) + "s"; doc["protocol"] = "wifi"; doc["detection_method"] = detection_type; doc["alert_level"] = "HIGH"; doc["device_category"] = "FLOCK_SAFETY"; // WiFi specific info doc["ssid"] = ssid; doc["ssid_length"] = strlen(ssid); doc["rssi"] = rssi; doc["signal_strength"] = rssi > -50 ? "STRONG" : (rssi > -70 ? "MEDIUM" : "WEAK"); doc["channel"] = current_channel; // MAC address info char mac_str[18]; snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); doc["mac_address"] = mac_str; char mac_prefix[9]; snprintf(mac_prefix, sizeof(mac_prefix), "%02x:%02x:%02x", mac[0], mac[1], mac[2]); doc["mac_prefix"] = mac_prefix; doc["vendor_oui"] = mac_prefix; // Detection pattern matching bool ssid_match = false; bool mac_match = false; for (int i = 0; i < sizeof(wifi_ssid_patterns)/sizeof(wifi_ssid_patterns[0]); i++) { if (strcasestr(ssid, wifi_ssid_patterns[i])) { doc["matched_ssid_pattern"] = wifi_ssid_patterns[i]; doc["ssid_match_confidence"] = "HIGH"; ssid_match = true; break; } } for (int i = 0; i < sizeof(mac_prefixes)/sizeof(mac_prefixes[0]); i++) { if (strncasecmp(mac_prefix, mac_prefixes[i], 8) == 0) { doc["matched_mac_pattern"] = mac_prefixes[i]; doc["mac_match_confidence"] = "HIGH"; mac_match = true; break; } } // Detection summary doc["detection_criteria"] = ssid_match && mac_match ? "SSID_AND_MAC" : (ssid_match ? "SSID_ONLY" : "MAC_ONLY"); doc["threat_score"] = ssid_match && mac_match ? 100 : (ssid_match || mac_match ? 85 : 70); // Frame type details if (strcmp(detection_type, "probe_request") == 0 || strcmp(detection_type, "probe_request_mac") == 0) { doc["frame_type"] = "PROBE_REQUEST"; doc["frame_description"] = "Device actively scanning for networks"; } else { doc["frame_type"] = "BEACON"; doc["frame_description"] = "Device advertising its network"; } String json_output; serializeJson(doc, json_output); Serial.println(json_output); } void output_ble_detection_json(const char* mac, const char* name, int rssi, const char* detection_method) { DynamicJsonDocument doc(2048); // Core detection info doc["timestamp"] = millis(); doc["detection_time"] = String(millis() / 1000.0, 3) + "s"; doc["protocol"] = "bluetooth_le"; doc["detection_method"] = detection_method; doc["alert_level"] = "HIGH"; doc["device_category"] = "FLOCK_SAFETY"; // BLE specific info doc["mac_address"] = mac; doc["rssi"] = rssi; doc["signal_strength"] = rssi > -50 ? "STRONG" : (rssi > -70 ? "MEDIUM" : "WEAK"); // Device name info if (name && strlen(name) > 0) { doc["device_name"] = name; doc["device_name_length"] = strlen(name); doc["has_device_name"] = true; } else { doc["device_name"] = ""; doc["device_name_length"] = 0; doc["has_device_name"] = false; } // MAC address analysis char mac_prefix[9]; strncpy(mac_prefix, mac, 8); mac_prefix[8] = '\0'; doc["mac_prefix"] = mac_prefix; doc["vendor_oui"] = mac_prefix; // Detection pattern matching bool name_match = false; bool mac_match = false; // Check MAC prefix patterns for (int i = 0; i < sizeof(mac_prefixes)/sizeof(mac_prefixes[0]); i++) { if (strncasecmp(mac, mac_prefixes[i], strlen(mac_prefixes[i])) == 0) { doc["matched_mac_pattern"] = mac_prefixes[i]; doc["mac_match_confidence"] = "HIGH"; mac_match = true; break; } } // Check device name patterns if (name && strlen(name) > 0) { for (int i = 0; i < sizeof(device_name_patterns)/sizeof(device_name_patterns[0]); i++) { if (strcasestr(name, device_name_patterns[i])) { doc["matched_name_pattern"] = device_name_patterns[i]; doc["name_match_confidence"] = "HIGH"; name_match = true; break; } } } // Detection summary doc["detection_criteria"] = name_match && mac_match ? "NAME_AND_MAC" : (name_match ? "NAME_ONLY" : "MAC_ONLY"); doc["threat_score"] = name_match && mac_match ? 100 : (name_match || mac_match ? 85 : 70); // BLE advertisement type analysis doc["advertisement_type"] = "BLE_ADVERTISEMENT"; doc["advertisement_description"] = "Bluetooth Low Energy device advertisement"; // Detection method details if (strcmp(detection_method, "mac_prefix") == 0) { doc["primary_indicator"] = "MAC_ADDRESS"; doc["detection_reason"] = "MAC address matches known Flock Safety prefix"; } else if (strcmp(detection_method, "device_name") == 0) { doc["primary_indicator"] = "DEVICE_NAME"; doc["detection_reason"] = "Device name matches Flock Safety pattern"; } String json_output; serializeJson(doc, json_output); Serial.println(json_output); } // ============================================================================ // DETECTION HELPER FUNCTIONS // ============================================================================ bool check_mac_prefix(const uint8_t* mac) { char mac_str[9]; // Only need first 3 octets for prefix check snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x", mac[0], mac[1], mac[2]); for (int i = 0; i < sizeof(mac_prefixes)/sizeof(mac_prefixes[0]); i++) { if (strncasecmp(mac_str, mac_prefixes[i], 8) == 0) { return true; } } return false; } bool check_ssid_pattern(const char* ssid) { if (!ssid) return false; for (int i = 0; i < sizeof(wifi_ssid_patterns)/sizeof(wifi_ssid_patterns[0]); i++) { if (strcasestr(ssid, wifi_ssid_patterns[i])) { return true; } } return false; } bool check_device_name_pattern(const char* name) { if (!name) return false; for (int i = 0; i < sizeof(device_name_patterns)/sizeof(device_name_patterns[0]); i++) { if (strcasestr(name, device_name_patterns[i])) { return true; } } return false; } // ============================================================================ // WIFI PROMISCUOUS MODE HANDLER // ============================================================================ typedef struct { unsigned frame_ctrl:16; unsigned duration_id:16; uint8_t addr1[6]; /* receiver address */ uint8_t addr2[6]; /* sender address */ uint8_t addr3[6]; /* filtering address */ unsigned sequence_ctrl:16; uint8_t addr4[6]; /* optional */ } wifi_ieee80211_mac_hdr_t; typedef struct { wifi_ieee80211_mac_hdr_t hdr; uint8_t payload[0]; /* network data ended with 4 bytes csum (CRC32) */ } wifi_ieee80211_packet_t; void wifi_sniffer_packet_handler(void* buff, wifi_promiscuous_pkt_type_t type) { if (triggered) return; const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buff; const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload; const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr; // Check for probe requests (subtype 0x04) and beacons (subtype 0x08) uint8_t frame_type = (hdr->frame_ctrl & 0xFF) >> 2; if (frame_type != 0x20 && frame_type != 0x80) { // Probe request or beacon return; } // Extract SSID from probe request or beacon char ssid[33] = {0}; uint8_t *payload = (uint8_t *)ipkt + 24; // Skip MAC header if (frame_type == 0x20) { // Probe request payload += 0; // Probe requests start with SSID immediately } else { // Beacon frame payload += 12; // Skip fixed parameters in beacon } // Parse SSID element (tag 0, length, data) if (payload[0] == 0 && payload[1] <= 32) { memcpy(ssid, &payload[2], payload[1]); ssid[payload[1]] = '\0'; } // Check if SSID matches our patterns if (strlen(ssid) > 0 && check_ssid_pattern(ssid)) { const char* detection_type = (frame_type == 0x20) ? "probe_request" : "beacon"; output_wifi_detection_json(ssid, hdr->addr2, ppkt->rx_ctrl.rssi, detection_type); if (!triggered) { triggered = true; flock_detected_beep_sequence(); } else { // Update detection time for heartbeat tracking last_detection_time = millis(); } return; } // Check MAC address if (check_mac_prefix(hdr->addr2)) { const char* detection_type = (frame_type == 0x20) ? "probe_request_mac" : "beacon_mac"; output_wifi_detection_json(ssid[0] ? ssid : "hidden", hdr->addr2, ppkt->rx_ctrl.rssi, detection_type); if (!triggered) { triggered = true; flock_detected_beep_sequence(); } else { // Update detection time for heartbeat tracking last_detection_time = millis(); } return; } } // ============================================================================ // BLE SCANNING // ============================================================================ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice* advertisedDevice) { if (triggered) return; NimBLEAddress addr = advertisedDevice->getAddress(); std::string addrStr = addr.toString(); uint8_t mac[6]; sscanf(addrStr.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); int rssi = advertisedDevice->getRSSI(); std::string name = ""; if (advertisedDevice->haveName()) { name = advertisedDevice->getName(); } // Check MAC prefix if (check_mac_prefix(mac)) { output_ble_detection_json(addrStr.c_str(), name.c_str(), rssi, "mac_prefix"); if (!triggered) { triggered = true; flock_detected_beep_sequence(); } else { // Update detection time for heartbeat tracking last_detection_time = millis(); } return; } // Check device name if (!name.empty() && check_device_name_pattern(name.c_str())) { output_ble_detection_json(addrStr.c_str(), name.c_str(), rssi, "device_name"); if (!triggered) { triggered = true; flock_detected_beep_sequence(); } else { // Update detection time for heartbeat tracking last_detection_time = millis(); } return; } } }; // ============================================================================ // CHANNEL HOPPING // ============================================================================ void hop_channel() { unsigned long now = millis(); if (now - last_channel_hop > CHANNEL_HOP_INTERVAL) { current_channel++; if (current_channel > MAX_CHANNEL) { current_channel = 1; } esp_wifi_set_channel(current_channel, WIFI_SECOND_CHAN_NONE); last_channel_hop = now; printf("Hopped to channel %d\n", current_channel); } } // ============================================================================ // MAIN FUNCTIONS // ============================================================================ void setup() { Serial.begin(115200); delay(1000); // Initialize buzzer pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); boot_beep_sequence(); printf("Starting Flock Squawk Enhanced Detection System...\n\n"); // Initialize WiFi in promiscuous mode WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); esp_wifi_set_promiscuous(true); esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler); esp_wifi_set_channel(current_channel, WIFI_SECOND_CHAN_NONE); printf("WiFi promiscuous mode enabled on channel %d\n", current_channel); printf("Monitoring probe requests and beacons...\n"); // Initialize BLE printf("Initializing BLE scanner...\n"); NimBLEDevice::init(""); pBLEScan = NimBLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->setInterval(100); pBLEScan->setWindow(99); printf("BLE scanner initialized\n"); printf("System ready - hunting for Flock Safety devices...\n\n"); last_channel_hop = millis(); } void loop() { // Handle channel hopping for WiFi promiscuous mode hop_channel(); // Handle heartbeat pulse if device is in range if (device_in_range) { unsigned long now = millis(); // Check if 10 seconds have passed since last heartbeat if (now - last_heartbeat >= 10000) { heartbeat_pulse(); last_heartbeat = now; } // Check if device has gone out of range (no detection for 30 seconds) if (now - last_detection_time >= 30000) { printf("Device out of range - stopping heartbeat\n"); device_in_range = false; triggered = false; // Allow new detections } } // Run BLE scan pBLEScan->start(1, false); // Scan for 1 second, don't clear results pBLEScan->clearResults(); delay(100); }