diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f37ff38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +.pio/ + +.vscode/ + +.DS_Store diff --git a/api/flockyou.py b/api/flockyou.py index 250ab54..11a2d32 100644 --- a/api/flockyou.py +++ b/api/flockyou.py @@ -249,43 +249,44 @@ def flock_reader(): """Background thread for reading Flock device data""" global flock_serial_connection, flock_device_connected, serial_data_buffer - while flock_device_connected: - if flock_serial_connection and flock_serial_connection.is_open: - try: - line = flock_serial_connection.readline().decode('utf-8', errors='ignore') - if line: - line = line.strip() + with app.app_context(): + while flock_device_connected: + if flock_serial_connection and flock_serial_connection.is_open: + try: + line = flock_serial_connection.readline().decode('utf-8', errors='ignore') if line: - # Store in buffer for terminal - serial_data_buffer.append(line) - if len(serial_data_buffer) > 1000: # Keep last 1000 lines - serial_data_buffer.pop(0) - - # Forward to all serial terminal clients - safe_socket_emit('serial_data', line, room='serial_terminal') - print(f"Serial data sent to terminal: {line}") - - # Try to parse as detection data - try: - data = json.loads(line) - if 'detection_method' in data: - # This is a detection, add it - add_detection_from_serial(data) - else: - print(f"JSON data without detection_method: {data}") - except json.JSONDecodeError: - # Not JSON, just log it - print(f"Flock device (non-JSON): {line}") + line = line.strip() + if line: + # Store in buffer for terminal + serial_data_buffer.append(line) + if len(serial_data_buffer) > 1000: # Keep last 1000 lines + serial_data_buffer.pop(0) - except Exception as e: - print(f"Flock device read error: {e}") - with connection_lock: - flock_device_connected = False - safe_socket_emit('flock_disconnected', {}) - # Trigger reconnection immediately - attempt_reconnect_flock() - break - time.sleep(0.1) + # Forward to all serial terminal clients + safe_socket_emit('serial_data', line, room='serial_terminal') + print(f"Serial data sent to terminal: {line}") + + # Try to parse as detection data + try: + data = json.loads(line) + if 'detection_method' in data: + # This is a detection, add it + add_detection_from_serial(data) + else: + print(f"JSON data without detection_method: {data}") + except json.JSONDecodeError: + # Not JSON, just log it + print(f"Flock device (non-JSON): {line}") + + except Exception as e: + print(f"Flock device read error: {e}") + with connection_lock: + flock_device_connected = False + safe_socket_emit('flock_disconnected', {}) + # Trigger reconnection immediately + attempt_reconnect_flock() + break + time.sleep(0.1) def find_best_gps_match(detection_timestamp): """Find the GPS reading closest in time to the detection timestamp""" @@ -487,50 +488,51 @@ def connection_monitor(): """Background thread for monitoring device connections""" global gps_enabled, flock_device_connected, serial_connection, reconnect_attempts - while True: - # Check GPS connection - if gps_enabled: - try: - if not serial_connection or not serial_connection.is_open: + with app.app_context(): + while True: + # Check GPS connection + if gps_enabled: + try: + if not serial_connection or not serial_connection.is_open: + with connection_lock: + gps_enabled = False + safe_socket_emit('gps_disconnected', {}) + print("GPS connection lost") + # Start reconnection attempts + attempt_reconnect_gps() + else: + # Test if the connection is still valid + serial_connection.in_waiting + except Exception as e: + print(f"GPS connection test failed: {e}") with connection_lock: gps_enabled = False safe_socket_emit('gps_disconnected', {}) - print("GPS connection lost") - # Start reconnection attempts attempt_reconnect_gps() - else: + + # Check Flock You device connection + if flock_device_connected: + try: # Test if the connection is still valid - serial_connection.in_waiting - except Exception as e: - print(f"GPS connection test failed: {e}") - with connection_lock: - gps_enabled = False - safe_socket_emit('gps_disconnected', {}) - attempt_reconnect_gps() - - # Check Flock You device connection - if flock_device_connected: - try: - # Test if the connection is still valid - if not flock_serial_connection or not flock_serial_connection.is_open: + if not flock_serial_connection or not flock_serial_connection.is_open: + with connection_lock: + flock_device_connected = False + safe_socket_emit('flock_disconnected', {}) + print("Flock You device connection lost") + # Start reconnection attempts + attempt_reconnect_flock() + else: + # Try a simple read to test connection + flock_serial_connection.in_waiting + except Exception as e: + print(f"Flock device connection test failed: {e}") with connection_lock: flock_device_connected = False safe_socket_emit('flock_disconnected', {}) - print("Flock You device connection lost") # Start reconnection attempts attempt_reconnect_flock() - else: - # Try a simple read to test connection - flock_serial_connection.in_waiting - except Exception as e: - print(f"Flock device connection test failed: {e}") - with connection_lock: - flock_device_connected = False - safe_socket_emit('flock_disconnected', {}) - # Start reconnection attempts - attempt_reconnect_flock() - - time.sleep(2) # Check every 2 seconds + + time.sleep(2) # Check every 2 seconds def attempt_reconnect_flock(): """Attempt to reconnect to Flock device""" @@ -539,46 +541,47 @@ def attempt_reconnect_flock(): def reconnect_thread(): global flock_device_connected, reconnect_attempts, flock_serial_connection - while not flock_device_connected and reconnect_attempts['flock'] < max_reconnect_attempts: - try: - print(f"Attempting to reconnect to Flock device (attempt {reconnect_attempts['flock'] + 1}/{max_reconnect_attempts})") - - # Try to reconnect - if flock_serial_connection: - try: - flock_serial_connection.close() - except: - pass - - # Wait a moment for the device to be ready - time.sleep(1) - - flock_serial_connection = serial.Serial(flock_device_port, 115200, timeout=1) - - # Test the connection - test_data = flock_serial_connection.readline() - - # If successful, update status - with connection_lock: - flock_device_connected = True - reconnect_attempts['flock'] = 0 - print(f"Successfully reconnected to Flock device on {flock_device_port}") - safe_socket_emit('flock_reconnected', {'port': flock_device_port}) - - # Restart the reading thread - flock_thread = threading.Thread(target=flock_reader, daemon=True) - flock_thread.start() - return - - except Exception as e: - print(f"Reconnection attempt failed: {e}") - reconnect_attempts['flock'] += 1 - time.sleep(reconnect_delay) - - if reconnect_attempts['flock'] >= max_reconnect_attempts: - print("Max reconnection attempts reached for Flock device") - safe_socket_emit('reconnect_failed', {'device': 'flock'}) - reconnect_attempts['flock'] = 0 # Reset for future attempts + with app.app_context(): + while not flock_device_connected and reconnect_attempts['flock'] < max_reconnect_attempts: + try: + print(f"Attempting to reconnect to Flock device (attempt {reconnect_attempts['flock'] + 1}/{max_reconnect_attempts})") + + # Try to reconnect + if flock_serial_connection: + try: + flock_serial_connection.close() + except: + pass + + # Wait a moment for the device to be ready + time.sleep(1) + + flock_serial_connection = serial.Serial(flock_device_port, 115200, timeout=1) + + # Test the connection + test_data = flock_serial_connection.readline() + + # If successful, update status + with connection_lock: + flock_device_connected = True + reconnect_attempts['flock'] = 0 + print(f"Successfully reconnected to Flock device on {flock_device_port}") + safe_socket_emit('flock_reconnected', {'port': flock_device_port}) + + # Restart the reading thread + flock_thread = threading.Thread(target=flock_reader, daemon=True) + flock_thread.start() + return + + except Exception as e: + print(f"Reconnection attempt failed: {e}") + reconnect_attempts['flock'] += 1 + time.sleep(reconnect_delay) + + if reconnect_attempts['flock'] >= max_reconnect_attempts: + print("Max reconnection attempts reached for Flock device") + safe_socket_emit('reconnect_failed', {'device': 'flock'}) + reconnect_attempts['flock'] = 0 # Reset for future attempts thread = threading.Thread(target=reconnect_thread, daemon=True) thread.start() @@ -589,36 +592,37 @@ def attempt_reconnect_gps(): def reconnect_thread(): global gps_enabled, reconnect_attempts - - while not gps_enabled and reconnect_attempts['gps'] < max_reconnect_attempts: - try: - print(f"Attempting to reconnect to GPS device (attempt {reconnect_attempts['gps'] + 1}/{max_reconnect_attempts})") - - # Try to reconnect - test_ser = serial.Serial(serial_connection.port, GPS_BAUDRATE, timeout=1) - test_ser.close() - - # If successful, update status - with connection_lock: - gps_enabled = True - reconnect_attempts['gps'] = 0 - print(f"Successfully reconnected to GPS device on {serial_connection.port}") - safe_socket_emit('gps_reconnected', {'port': serial_connection.port}) - - # Restart the reading thread - gps_thread = threading.Thread(target=gps_reader, daemon=True) - gps_thread.start() - return - - except Exception as e: - print(f"GPS reconnection attempt failed: {e}") - reconnect_attempts['gps'] += 1 - time.sleep(reconnect_delay) - - if reconnect_attempts['gps'] >= max_reconnect_attempts: - print("Max reconnection attempts reached for GPS device") - safe_socket_emit('reconnect_failed', {'device': 'gps'}) - reconnect_attempts['gps'] = 0 # Reset for future attempts + + with app.app_context(): + while not gps_enabled and reconnect_attempts['gps'] < max_reconnect_attempts: + try: + print(f"Attempting to reconnect to GPS device (attempt {reconnect_attempts['gps'] + 1}/{max_reconnect_attempts})") + + # Try to reconnect + test_ser = serial.Serial(serial_connection.port, GPS_BAUDRATE, timeout=1) + test_ser.close() + + # If successful, update status + with connection_lock: + gps_enabled = True + reconnect_attempts['gps'] = 0 + print(f"Successfully reconnected to GPS device on {serial_connection.port}") + safe_socket_emit('gps_reconnected', {'port': serial_connection.port}) + + # Restart the reading thread + gps_thread = threading.Thread(target=gps_reader, daemon=True) + gps_thread.start() + return + + except Exception as e: + print(f"GPS reconnection attempt failed: {e}") + reconnect_attempts['gps'] += 1 + time.sleep(reconnect_delay) + + if reconnect_attempts['gps'] >= max_reconnect_attempts: + print("Max reconnection attempts reached for GPS device") + safe_socket_emit('reconnect_failed', {'device': 'gps'}) + reconnect_attempts['gps'] = 0 # Reset for future attempts thread = threading.Thread(target=reconnect_thread, daemon=True) thread.start() @@ -1208,13 +1212,14 @@ def handle_heartbeat(): def send_heartbeat(): """Send periodic heartbeat to all clients""" - while True: - try: - safe_socket_emit('heartbeat', {}) - time.sleep(30) # Send heartbeat every 30 seconds - except Exception as e: - print(f"Heartbeat error: {e}") - time.sleep(5) + with app.app_context(): + while True: + try: + safe_socket_emit('heartbeat', {}) + time.sleep(30) # Send heartbeat every 30 seconds + except Exception as e: + print(f"Heartbeat error: {e}") + time.sleep(5) @socketio.on('request_serial_terminal') def handle_serial_terminal_request(data): diff --git a/platformio.ini b/platformio.ini index 2f9c8e2..e09eafc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,3 +14,20 @@ build_flags = -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DCONFIG_BT_NIMBLE_ENABLED=1 + +[env:xiao_esp32c3] +platform = espressif32 +board = seeed_xiao_esp32c3 +framework = arduino +monitor_speed = 115200 +board_build.partitions = huge_app.csv +board_build.flash_mode = qio +board_build.flash_size = 4MB +board_build.psram_type = opi +lib_deps = + h2zero/NimBLE-Arduino@^1.4.0 + bblanchon/ArduinoJson@^6.21.0 +build_flags = + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DCONFIG_BT_NIMBLE_ENABLED=1 diff --git a/src/main.cpp b/src/main.cpp index d2c0c36..bf3cab2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,12 @@ // WiFi Promiscuous Mode Configuration #define MAX_CHANNEL 13 -#define CHANNEL_HOP_INTERVAL 2500 // milliseconds +#define CHANNEL_HOP_INTERVAL 500 // milliseconds + +// BLE SCANNING CONFIGURATION +#define BLE_SCAN_DURATION 1 // Seconds +#define BLE_SCAN_INTERVAL 5000 // Milliseconds between scans +static unsigned long last_ble_scan = 0; // Detection Pattern Limits #define MAX_SSID_PATTERNS 10 @@ -58,13 +63,14 @@ static const char* mac_prefixes[] = { // 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", + "74:4c:a1", "08:3a:88", "9c:2f:9d", "94:08:53", "e4:aa:ea" - // 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" + // Penguin devices - these are NOT OUI based, so use local ouis + // from the wigle.net db relative to your location + // "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 @@ -468,7 +474,7 @@ void hop_channel() } esp_wifi_set_channel(current_channel, WIFI_SECOND_CHAN_NONE); last_channel_hop = now; - printf("Hopped to channel %d\n", current_channel); + printf("[WiFi] Hopped to channel %d\n", current_channel); } } @@ -538,9 +544,15 @@ void loop() } } - // Run BLE scan - pBLEScan->start(1, false); // Scan for 1 second, don't clear results - pBLEScan->clearResults(); + if (millis() - last_ble_scan >= BLE_SCAN_INTERVAL && !pBLEScan->isScanning()) { + printf("[BLE] scan...\n"); + pBLEScan->start(BLE_SCAN_DURATION, false); + last_ble_scan = millis(); + } + + if (pBLEScan->isScanning() == false && millis() - last_ble_scan > BLE_SCAN_DURATION * 1000) { + pBLEScan->clearResults(); + } delay(100); }