Files
flock-you/src/main.cpp
T
Colonel Panic d8c52f6d58 Update main.cpp
2025-08-22 12:23:25 -04:00

555 lines
18 KiB
C++

#include <Arduino.h>
#include <WiFi.h>
#include <NimBLEDevice.h>
#include <NimBLEScan.h>
#include <NimBLEAdvertisedDevice.h>
#include <ArduinoJson.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#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);
}