soorajSryshnav CMMohammed_ShibinSreya. V
Published

Fall Detection System

This project implements a real-time Fall Detection System using the Arduino Nano microcontroller, designed to enhance the safety of elderly

IntermediateFull instructions provided104
Fall Detection System

Things used in this project

Hardware components

Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×1
Buzzer
Buzzer
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
3800mah battery
×1

Software apps and online services

Arduino IDE
Arduino IDE
Edge Impulse Studio
Edge Impulse Studio
Visual Studio 2017
Microsoft Visual Studio 2017

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

3D Casing

3D printed wearable watch model used to hold the components.

Schematics

Fall detection

circuit diagram showing connection between the components

Code

Fall detection Telegram

Python
python code used for to read data from sensor and send messages to telegram bot.
import asyncio
import json
import logging
from datetime import datetime
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
from bleak import BleakClient, BleakScanner
from typing import Final
import time
import sys

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Telegram Configuration - REPLACE WITH YOUR VALUES
TOKEN: Final = "8200016643:AAGxX25lJHHTu8RKAIrmgXEJ_3Riutb94qU"  # Replace with your bot token
BOT_USERNAME: Final = '@detectmyfall_bot'  # Replace with your bot username

# BLE Configuration
SERVICE_UUID = "19B10000-E8F2-537E-4F6C-D104768A1215"
PREDICTION_CHAR_UUID = "19B10001-E8F2-537E-4F6C-D104768A1215"
CLASSES_CHAR_UUID = "19B10002-E8F2-537E-4F6C-D104768A1215"
DEVICE_NAME = "FallDetector"

# Global variables
subscribers = set()
emergency_contacts = set()  # Special subscribers for emergency alerts
chunk_buffer = {}
is_monitoring = False
auto_reconnect = True
ble_client = None
device_address = None
application = None
fall_alert_active = False
last_fall_time = 0
pending_fall_notified = False  # Track if we've already notified about pending fall

# Emergency settings
FALL_CONFIDENCE_THRESHOLD = 0.99  # Threshold for potential fall detection
EMERGENCY_COOLDOWN = 300  # 5 minutes cooldown between emergency alerts


def escape_html(text):
    """Escape HTML special characters"""
    if not isinstance(text, str):
        text = str(text)
    return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')


def format_prediction_message(data, fall_state="normal", is_emergency=False):
    """Format prediction data for Telegram"""
    try:
        timestamp = datetime.now().strftime("%H:%M:%S")

        if is_emergency:
            message = f" <b>EMERGENCY: FALL CONFIRMED!</b>\n {timestamp}\n\n"
        elif fall_state == "pending":
            message = f" <b>POTENTIAL FALL DETECTED</b>\n {timestamp}\n\n"
        else:
            message = f" <b>Movement Detection</b>\n {timestamp}\n\n"

        # Top prediction
        top_activity = escape_html(data.get('top', 'Unknown'))
        confidence = data.get('conf', 0) * 100

        # Activity emojis (only for your two classes: Stand and Fall)
        activity_emojis = {
            'fall': '',
            'stand': ''
        }

        emoji = activity_emojis.get(top_activity.lower(), '')

        if is_emergency:
            message += f" <b>{top_activity.upper()}</b> ({confidence:.1f}%)\n\n"
            message += " <b>FALL CONFIRMED - IMMEDIATE ATTENTION REQUIRED!</b>\n"
            message += " Check on the person immediately\n"
            message += " Consider calling emergency services\n"
            message += " No button press detected within 30 seconds\n\n"
        elif fall_state == "pending":
            message += f"{emoji} <b>Potential {top_activity.title()}</b> ({confidence:.1f}%)\n\n"
            message += " <b>30-second confirmation window active</b>\n"
            message += " Person can press button to cancel false alarm\n"
            message += " Will become emergency if no button press\n\n"
        else:
            message += f"{emoji} <b>{top_activity.title()}</b> ({confidence:.1f}%)\n\n"

        # Top 2 predictions (since you only have Stand and Fall)
        predictions = data.get('predictions', [])
        if predictions:
            sorted_preds = sorted(predictions, key=lambda x: x.get('c', 0), reverse=True)
            message += "<b>Detection Breakdown:</b>\n"
            for pred in sorted_preds:  # Show all predictions (only 2)
                label = escape_html(pred.get('l', 'Unknown')).title()
                conf = pred.get('c', 0) * 100
                pred_emoji = activity_emojis.get(label.lower(), '')

                # Highlight falls with special formatting
                if label.lower() == 'fall' and conf > 50:
                    if fall_state == "pending":
                        message += f" <b>{label}: {conf:.1f}%</b> (Pending confirmation)\n"
                    elif is_emergency:
                        message += f" <b>{label}: {conf:.1f}%</b> (CONFIRMED)\n"
                    else:
                        message += f" <b>{label}: {conf:.1f}%</b>\n"
                else:
                    message += f"{pred_emoji} {label}: {conf:.1f}%\n"

        # Anomaly detection
        if 'anom' in data and data['anom'] > 0.7:
            message += f"\n <b>Unusual pattern detected:</b> {data['anom']:.2f}"

        # Add fall state specific information
        if fall_state == "pending":
            message += f"\n <i>Waiting for user confirmation...</i>"
        elif fall_state == "confirmed":
            message += f"\n <i>Fall confirmed - no button press within timeout</i>"
        elif fall_state == "normal" and top_activity.lower() == 'fall':
            message += f"\n <i>Confidence below threshold ({FALL_CONFIDENCE_THRESHOLD * 100}%)</i>"

        return message

    except Exception as e:
        logger.error(f"Error formatting message: {e}")
        timestamp = datetime.now().strftime("%H:%M:%S")
        if is_emergency:
            return f" <b>EMERGENCY ALERT at {timestamp}</b>\n(Error formatting details)"
        else:
            return f" <b>Detection at {timestamp}</b>\n(Error formatting details)"


def handle_chunked_data(chunk_data):
    """Handle chunked BLE data transmission"""
    global chunk_buffer

    try:
        if ':' not in chunk_data:
            return chunk_data

        parts = chunk_data.split(':', 2)
        if len(parts) != 3:
            return chunk_data

        try:
            chunk_index = int(parts[0])
            total_chunks = int(parts[1])
            chunk_content = parts[2]
        except ValueError:
            return chunk_data

        logger.info(f"Received chunk {chunk_index + 1}/{total_chunks}")

        # Initialize buffer for new transmission
        if chunk_index == 0:
            chunk_buffer = {
                'total': total_chunks,
                'chunks': {},
                'timestamp': time.time()
            }

        if 'total' not in chunk_buffer:
            return None

        # Store chunk
        chunk_buffer['chunks'][chunk_index] = chunk_content

        # Check if complete
        if len(chunk_buffer['chunks']) == total_chunks:
            complete_data = ""
            for i in range(total_chunks):
                if i in chunk_buffer['chunks']:
                    complete_data += chunk_buffer['chunks'][i]
                else:
                    chunk_buffer = {}
                    return None

            chunk_buffer = {}
            logger.info(f"Reconstructed complete data: {len(complete_data)} chars")
            return complete_data

        return None

    except Exception as e:
        logger.error(f"Error handling chunked data: {e}")
        chunk_buffer = {}
        return chunk_data


async def send_to_subscribers(message, emergency=False):
    """Send message to subscribers (emergency alerts go to emergency contacts too)"""
    global subscribers, emergency_contacts, application

    if not application:
        return

    target_subscribers = subscribers.copy()
    if emergency:
        target_subscribers.update(emergency_contacts)

    if not target_subscribers:
        return

    failed_subscribers = []
    for chat_id in list(target_subscribers):
        try:
            await application.bot.send_message(
                chat_id=chat_id,
                text=message,
                parse_mode='HTML'
            )
            logger.info(f"Message sent to {chat_id} (emergency: {emergency})")
        except Exception as e:
            logger.error(f"Failed to send to {chat_id}: {e}")
            failed_subscribers.append(chat_id)

    # Remove failed subscribers
    for chat_id in failed_subscribers:
        subscribers.discard(chat_id)
        emergency_contacts.discard(chat_id)


async def notification_handler(sender, data):
    """Handle BLE notifications - Respond to fall confirmation states"""
    global fall_alert_active, last_fall_time, pending_fall_notified

    try:
        decoded_data = data.decode('utf-8')
        logger.info(f"Received BLE data: {decoded_data}")

        # Handle chunked data
        complete_data = handle_chunked_data(decoded_data)
        if complete_data is None:
            return

        logger.info(f"Processing complete data: {complete_data}")

        # Parse JSON
        try:
            prediction_data = json.loads(complete_data)
            logger.info(f"Parsed prediction data: {prediction_data}")
        except json.JSONDecodeError as e:
            logger.error(f"Invalid JSON: {e}")
            return

        # Get fall state from Arduino
        fall_state = prediction_data.get('fall_state', 'normal')  # normal, pending, confirmed
        top_prediction = prediction_data.get('top', '').lower()
        confidence = prediction_data.get('conf', 0)
        current_time = time.time()

        logger.info(f"Fall state: {fall_state}, Prediction: {top_prediction}, Confidence: {confidence:.3f}")
        print(fall_state)
        # Handle different fall states
        if fall_state == "pending":
            # Fall detected, waiting for button confirmation
            if not pending_fall_notified:  # Only send once per pending event
                logger.warning(f"POTENTIAL FALL DETECTED! Waiting for confirmation. Confidence: {confidence:.3f}")

                # Send pending fall notification to all subscribers
                pending_message = format_prediction_message(prediction_data, fall_state="pending", is_emergency=False)
                await send_to_subscribers(pending_message, emergency=False)

                # Also notify emergency contacts about pending fall
                if emergency_contacts:
                    emergency_pending_message = format_prediction_message(prediction_data, fall_state="pending",
                                                                          is_emergency=False)
                    await send_to_subscribers(emergency_pending_message, emergency=True)

                pending_fall_notified = True
                logger.info("Pending fall notification sent")

        elif fall_state == "confirmed":
            # Fall confirmed after 30 seconds without button press
            logger.critical(f"FALL CONFIRMED! No button press detected. Confidence: {confidence:.3f}")

            fall_alert_active = True
            last_fall_time = current_time
            pending_fall_notified = False  # Reset for next event

            # Send emergency alert to all subscribers
            emergency_message = format_prediction_message(prediction_data, fall_state="confirmed", is_emergency=True)
            await send_to_subscribers(emergency_message, emergency=False)

            # Send to emergency contacts as well
            if emergency_contacts:
                await send_to_subscribers(emergency_message, emergency=True)

            logger.critical("CONFIRMED FALL - Emergency notifications sent")

        elif fall_state == "timeout" or fall_state == "confirmed_timeout":
            # Fall timed out without button press (same as confirmed)
            logger.critical(f"FALL TIMEOUT! No button press within 30 seconds. Confidence: {confidence:.3f}")

            fall_alert_active = True
            last_fall_time = current_time
            pending_fall_notified = False  # Reset for next event

            # Send emergency alert to all subscribers
            emergency_message = format_prediction_message(prediction_data, fall_state="confirmed", is_emergency=True)
            await send_to_subscribers(emergency_message, emergency=False)

            # Send to emergency contacts as well
            if emergency_contacts:
                await send_to_subscribers(emergency_message, emergency=True)

            logger.critical("FALL TIMEOUT - Emergency notifications sent")

        elif fall_state == "cancelled" or fall_state == "canceled":
            # Fall was cancelled by button press
            logger.info("Fall was cancelled by button press")

            if pending_fall_notified:
                # Send cancellation message
                cancellation_message = f" <b>Fall Alert Cancelled</b>\n {datetime.now().strftime('%H:%M:%S')}\n\n"
                cancellation_message += " Button pressed - false alarm cancelled\n"
                cancellation_message += " Returning to normal monitoring"

                await send_to_subscribers(cancellation_message, emergency=False)
                if emergency_contacts:
                    await send_to_subscribers(cancellation_message, emergency=True)

                logger.info("Fall cancellation notification sent")

            pending_fall_notified = False

        elif fall_state == "normal":
            # Normal state - reset pending notification flag if transitioning from pending
            if pending_fall_notified:
                # This might be a transition from pending to normal (button press)
                logger.info("Fall was cancelled - returning to normal state")

                # Send cancellation message
                cancellation_message = f" <b>Fall Alert Cancelled</b>\n {datetime.now().strftime('%H:%M:%S')}\n\n"
                cancellation_message += " Button pressed - false alarm cancelled\n"
                cancellation_message += " Returning to normal monitoring"

                await send_to_subscribers(cancellation_message, emergency=False)
                if emergency_contacts:
                    await send_to_subscribers(cancellation_message, emergency=True)

                pending_fall_notified = False
                logger.info("Fall cancellation notification sent")

            # For normal high-confidence falls below emergency threshold, optionally notify
            elif top_prediction == 'fall' and confidence >= 0.5 and confidence < FALL_CONFIDENCE_THRESHOLD:
                logger.info(f"Low-confidence fall detected but below emergency threshold: {confidence:.3f}")
                # Uncomment below if you want notifications for lower-confidence falls
                # message = format_prediction_message(prediction_data, fall_state="normal", is_emergency=False)
                # await send_to_subscribers(message, emergency=False)

            # For all other normal cases (standing, very low confidence falls), do nothing
            else:
                logger.info(f"Normal activity detected: {top_prediction} ({confidence:.3f}) - No notification sent")

        elif fall_state.lower() == "stopped":
            # Arduino has stopped fall detection (button pressed to stop system)
            logger.info("Fall detection system stopped by user")

            if pending_fall_notified:
                # If there was a pending fall, treat this as cancellation
                cancellation_message = f" <b>Fall Detection Stopped</b>\n {datetime.now().strftime('%H:%M:%S')}\n\n"
                cancellation_message += " System stopped by user button press\n"
                cancellation_message += " Fall detection is now inactive\n"
                cancellation_message += "Press button again to resume monitoring"

                await send_to_subscribers(cancellation_message, emergency=False)
                if emergency_contacts:
                    await send_to_subscribers(cancellation_message, emergency=True)

                pending_fall_notified = False
                logger.info("Fall detection stopped notification sent")
            else:
                # Just a regular stop
                stop_message = f" <b>Fall Detection Stopped</b>\n {datetime.now().strftime('%H:%M:%S')}\n\n"
                stop_message += "Fall detection has been paused\n"
                stop_message += "Press button to resume monitoring"

                await send_to_subscribers(stop_message, emergency=False)
                logger.info("System stop notification sent")

        elif fall_state.lower() == "resumed" or fall_state.lower() == "active":
            # Arduino has resumed fall detection
            logger.info("Fall detection system resumed")

            resume_message = f" <b>Fall Detection Resumed</b>\n {datetime.now().strftime('%H:%M:%S')}\n\n"
            resume_message += " Fall detection is now active\n"
            resume_message += " Monitoring for falls..."

            await send_to_subscribers(resume_message, emergency=False)
            logger.info("Fall detection resumed notification sent")

            # Reset any pending states
            pending_fall_notified = False
            fall_alert_active = False

        else:
            # Handle any other unknown states
            logger.warning(f"Unknown fall state received: {fall_state}")

            # If we had a pending fall and got an unknown state, assume timeout
            if pending_fall_notified and fall_state not in ["normal", "pending", "cancelled", "canceled"]:
                logger.warning(f"Pending fall timeout with unknown state: {fall_state}")

                # Treat as confirmed fall
                fall_alert_active = True
                last_fall_time = current_time
                pending_fall_notified = False

                emergency_message = format_prediction_message(prediction_data, fall_state="confirmed",
                                                              is_emergency=True)
                await send_to_subscribers(emergency_message, emergency=False)

                if emergency_contacts:
                    await send_to_subscribers(emergency_message, emergency=True)

                logger.critical("Unknown state treated as confirmed fall")

    except Exception as e:
        logger.error(f"Notification handler error: {e}")


async def connect_to_device():
    """Connect to BLE device with improved connection handling"""
    global ble_client, device_address, is_monitoring

    max_retries = 5
    discovery_timeout = 20.0

    for attempt in range(max_retries):
        try:
            print(f'Looking for {DEVICE_NAME}... (Attempt {attempt + 1}/{max_retries})')

            # Scan for devices with longer timeout and more attempts
            devices = await BleakScanner.discover(timeout=discovery_timeout)
            target_device = None

            print(f"Found {len(devices)} BLE devices:")
            for device in devices:
                device_name = device.name or "None"
                print(f"  - {device_name} ({device.address})")
                if device.name and DEVICE_NAME in device.name:
                    print(f' Found target device: {DEVICE_NAME} at {device.address}')
                    target_device = device
                    device_address = device.address
                    break

            if not target_device:
                print(f' Could not find {DEVICE_NAME} in attempt {attempt + 1}')
                if attempt < max_retries - 1:
                    print(f'Waiting 3 seconds before retry...')
                    await asyncio.sleep(3)
                    continue
                else:
                    return False

            # Disconnect any existing client first
            if ble_client and ble_client.is_connected:
                try:
                    await ble_client.disconnect()
                    await asyncio.sleep(1)  # Give it time to disconnect
                except:
                    pass

            # Create new client with connection parameters
            print(f' Attempting to connect to {device_address}...')
            ble_client = BleakClient(
                target_device.address,
                timeout=30.0,  # 30 second timeout
                disconnected_callback=lambda client: print(" Device disconnected callback triggered")
            )

            # Try connection with multiple attempts
            connection_attempts = 3
            connected = False

            for conn_attempt in range(connection_attempts):
                try:
                    print(f'   Connection attempt {conn_attempt + 1}/{connection_attempts}...')
                    await ble_client.connect()

                    # Verify connection
                    if ble_client.is_connected:
                        print(f' Successfully connected to {device_address}')
                        connected = True
                        break
                    else:
                        print(f' Connection reported success but is_connected is False')

                except Exception as conn_e:
                    print(f' Connection attempt {conn_attempt + 1} failed: {conn_e}')
                    if conn_attempt < connection_attempts - 1:
                        await asyncio.sleep(2)
                        continue

            if not connected:
                print(f' All connection attempts failed for attempt {attempt + 1}')
                if ble_client:
                    try:
                        await ble_client.disconnect()
                    except:
                        pass
                    ble_client = None

                if attempt < max_retries - 1:
                    print(f'Waiting 5 seconds before next device discovery...')
                    await asyncio.sleep(5)
                continue

            # Verify services are available with retry
            service_attempts = 3
            service_found = False

            for svc_attempt in range(service_attempts):
                try:
                    print(f' Checking available services (attempt {svc_attempt + 1})...')

                    # Add small delay before checking services
                    await asyncio.sleep(1)

                    services = ble_client.services

                    if not services:
                        print(f' No services found on attempt {svc_attempt + 1}')
                        if svc_attempt < service_attempts - 1:
                            await asyncio.sleep(2)
                            continue
                        else:
                            break

                    for service in services:
                        print(f"  Service: {service.uuid}")
                        if str(service.uuid).upper() == SERVICE_UUID.upper():
                            service_found = True
                            print(f"   Found required service: {SERVICE_UUID}")
                            break

                    if service_found:
                        break
                    else:
                        print(f' Required service not found on attempt {svc_attempt + 1}')
                        if svc_attempt < service_attempts - 1:
                            await asyncio.sleep(2)

                except Exception as service_e:
                    print(f' Error checking services on attempt {svc_attempt + 1}: {service_e}')
                    if svc_attempt < service_attempts - 1:
                        await asyncio.sleep(2)

            if not service_found:
                print(f' Required service {SERVICE_UUID} not found after all attempts')
                await ble_client.disconnect()
                ble_client = None
                if attempt < max_retries - 1:
                    await asyncio.sleep(5)
                continue

            # Set up notifications with retry
            notification_attempts = 3
            notifications_started = False

            for notif_attempt in range(notification_attempts):
                try:
                    print(f' Setting up notifications (attempt {notif_attempt + 1})...')
                    await asyncio.sleep(1)  # Small delay

                    await ble_client.start_notify(PREDICTION_CHAR_UUID, notification_handler)
                    is_monitoring = True
                    notifications_started = True
                    print(' Notifications started successfully')
                    break

                except Exception as notify_e:
                    print(f' Failed to start notifications on attempt {notif_attempt + 1}: {notify_e}')
                    if notif_attempt < notification_attempts - 1:
                        await asyncio.sleep(2)

            if not notifications_started:
                print(f' Failed to start notifications after all attempts')
                await ble_client.disconnect()
                ble_client = None
                if attempt < max_retries - 1:
                    await asyncio.sleep(5)
                continue

            # Read classes if available (optional)
            try:
                print(' Reading available classes...')
                await asyncio.sleep(1)
                classes_data = await ble_client.read_gatt_char(CLASSES_CHAR_UUID)
                classes_json = classes_data.decode('utf-8')
                logger.info(f"Available classes: {classes_json}")
                print(' Classes read successfully')
            except Exception as e:
                logger.warning(f"Could not read classes (this is optional): {e}")
                print(' Could not read classes (this is optional)')

            await send_to_subscribers(
                " <b>Fall Detector Connected</b>\n Monitoring with button confirmation system active!")
            print(' BLE connection successful!')
            return True

        except Exception as e:
            logger.error(f"Connection attempt {attempt + 1} failed: {e}")
            print(f" Connection attempt {attempt + 1} failed: {e}")

            if ble_client:
                try:
                    await ble_client.disconnect()
                except:
                    pass
                ble_client = None

            if attempt < max_retries - 1:
                print(f'Waiting 5 seconds before retry...')
                await asyncio.sleep(5)

    print(f' Could not connect to {DEVICE_NAME} after {max_retries} attempts')
    return False


async def disconnect_device():
    """Disconnect from BLE device"""
    global ble_client, is_monitoring, fall_alert_active, pending_fall_notified

    try:
        is_monitoring = False
        fall_alert_active = False
        pending_fall_notified = False
        if ble_client and ble_client.is_connected:
            await ble_client.disconnect()
            ble_client = None
        await send_to_subscribers(" <b>Fall Detector Disconnected</b>")
        logger.info("Disconnected from device")

    except Exception as e:
        logger.error(f"Disconnect error: {e}")


# Command handlers
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Start command handler"""
    chat_id = update.message.chat.id
    subscribers.add(chat_id)

    message = " <b>Fall Detector Bot with Button Confirmation</b>\n\n"
    message += "Welcome! This bot monitors for falls using an Arduino sensor with push button confirmation.\n\n"
    message += "<b>How the confirmation system works:</b>\n"
    message += " When a potential fall is detected, you have 30 seconds to press the button\n"
    message += " Button press = False alarm cancelled\n"
    message += " No button press = Fall confirmed, emergency alert sent\n\n"
    message += "<b>Commands:</b>\n"
    message += " /start - Subscribe to notifications\n"
    message += " /stop - Unsubscribe\n"
    message += " /emergency - Subscribe to emergency alerts\n"
    message += " /status - Check status\n"
    message += " /connect - Connect to device\n"
    message += " /disconnect - Disconnect\n"
    message += " /reconnect - Toggle auto-reconnect\n"
    message += " /silence - Silence current fall alert\n"
    message += " /help - Show help\n\n"
    message += " Make sure your Arduino is powered on!\n\n"
    message += " <b>Important:</b> This is a monitoring tool, not a replacement for professional medical alert systems."

    await update.message.reply_text(message, parse_mode='HTML')
    logger.info(f"User {chat_id} subscribed")


async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Stop command handler"""
    chat_id = update.message.chat.id
    subscribers.discard(chat_id)
    emergency_contacts.discard(chat_id)
    await update.message.reply_text(
        " <b>Unsubscribed</b>\n\nYou've been removed from both regular and emergency notifications.\nUse /start to subscribe again",
        parse_mode='HTML'
    )
    logger.info(f"User {chat_id} unsubscribed")


async def emergency_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Emergency subscription command"""
    chat_id = update.message.chat.id
    subscribers.add(chat_id)  # Also add to regular subscribers
    emergency_contacts.add(chat_id)

    message = " <b>Emergency Contact Added</b>\n\n"
    message += " You'll receive ALL notifications including:\n"
    message += " Pending fall alerts (30-second countdown)\n"
    message += " Confirmed fall emergencies\n"
    message += " Fall cancellation notifications\n"
    message += " System status updates\n\n"
    message += " <b>Emergency alerts are only sent AFTER fall confirmation (30 seconds without button press).</b>\n\n"
    message += "Use /stop to unsubscribe from all notifications."

    await update.message.reply_text(message, parse_mode='HTML')
    logger.info(f"User {chat_id} added as emergency contact")


async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Status command handler"""
    connection_status = " Connected" if is_monitoring else " Disconnected"
    subscriber_count = len(subscribers)
    emergency_count = len(emergency_contacts)
    auto_reconnect_status = " Enabled" if auto_reconnect else " Disabled"
    device_addr = escape_html(device_address or "Not found")
    alert_status = " ACTIVE" if fall_alert_active else " Clear"
    pending_status = " PENDING" if pending_fall_notified else " None"

    message = f"<b> Fall Detector Status</b>\n\n"
    message += f"<b>Device:</b> {connection_status}\n"
    message += f"<b>Address:</b> <code>{device_addr}</code>\n"
    message += f"<b>Alert Status:</b> {alert_status}\n"
    message += f"<b>Pending Falls:</b> {pending_status}\n"
    message += f"<b>Subscribers:</b> {subscriber_count}\n"
    message += f"<b>Emergency Contacts:</b> {emergency_count}\n"
    message += f"<b>Auto-reconnect:</b> {auto_reconnect_status}\n\n"

    if is_monitoring:
        message += " Monitoring with button confirmation system!\n"
        message += " 30-second confirmation window active\n"
        if fall_alert_active:
            message += " <b>Fall alert is currently active</b>\n"
            message += "Use /silence to acknowledge"
        elif pending_fall_notified:
            message += " <b>Fall confirmation pending</b>\n"
            message += "Waiting for button press or timeout"
    else:
        message += " Use /connect to start monitoring"

    await update.message.reply_text(message, parse_mode='HTML')


async def connect_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Connect command"""
    await update.message.reply_text(" <b>Connecting...</b>\n\nThis may take up to 60 seconds...", parse_mode='HTML')

    if await connect_to_device():
        await update.message.reply_text(
            " <b>Connected!</b>\n Monitoring with button confirmation system active...\n 30-second confirmation window enabled",
            parse_mode='HTML'
        )
    else:
        await update.message.reply_text(
            " <b>Connection failed</b>\n\n"
            "<b>Troubleshooting:</b>\n"
            " Make sure Arduino is powered on and advertising\n"
            " Reset the Arduino (power cycle)\n"
            " Check that device is nearby (&lt; 5m for connection)\n"
            " Ensure no other devices are connected to it\n"
            " Make sure the Arduino sketch is running properly\n"
            " Try restarting this bot\n"
            " Check Arduino serial monitor for BLE status",
            parse_mode='HTML'
        )


async def disconnect_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Disconnect command"""
    await disconnect_device()
    await update.message.reply_text(
        " <b>Disconnected</b>\n\nUse /connect to reconnect",
        parse_mode='HTML'
    )


async def reconnect_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Toggle auto-reconnect"""
    global auto_reconnect
    auto_reconnect = not auto_reconnect
    status = "enabled" if auto_reconnect else "disabled"
    emoji = "" if auto_reconnect else ""

    await update.message.reply_text(
        f"{emoji} <b>Auto-reconnect {status}</b>",
        parse_mode='HTML'
    )


async def silence_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Silence current fall alert"""
    global fall_alert_active, pending_fall_notified

    if fall_alert_active:
        fall_alert_active = False
        await update.message.reply_text(
            " <b>Fall alert silenced</b>\n\n Alert acknowledged\n Monitoring continues...",
            parse_mode='HTML'
        )
        # Notify all subscribers that alert was acknowledged
        await send_to_subscribers(
            f" <b>Fall Alert Acknowledged</b>\n\n Alert silenced by user\n {datetime.now().strftime('%H:%M:%S')}"
        )
        logger.info("Fall alert silenced by user")
    elif pending_fall_notified:
        await update.message.reply_text(
            " <b>Fall confirmation pending</b>\n\n Waiting for physical button press on device\n Cannot silence from Telegram during confirmation window",
            parse_mode='HTML'
        )
    else:
        await update.message.reply_text(
            " <b>No active fall alert</b>\n\nThere's currently no fall alert to silence.",
            parse_mode='HTML'
        )


async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Help command"""
    message = " <b>Fall Detector Bot with Button Confirmation</b>\n\n"
    message += "<b>How it works:</b>\n"
    message += "Connects to Arduino Nano BLE Sense 33 with IMU sensor for real-time fall detection using AI with button confirmation system.\n\n"
    message += "<b>Button Confirmation System:</b>\n"
    message += "1 AI detects potential fall\n"
    message += "2 30-second countdown begins\n"
    message += "3 Press button to cancel false alarm\n"
    message += "4 No button press = Emergency alert sent\n\n"
    message += "<b>Setup:</b>\n"
    message += "1 Flash Arduino code to your device\n"
    message += "2 Connect push button to Arduino\n"
    message += "3 Power on Arduino\n"
    message += "4 Use /start to subscribe\n"
    message += "5 Use /emergency for critical alerts\n"
    message += "6 Use /connect to connect\n"
    message += "7 Test the system!\n\n"
    message += "<b>Commands:</b>\n"
    message += "/start - Subscribe to notifications\n"
    message += "/stop - Unsubscribe\n"
    message += "/emergency - Subscribe to emergency alerts\n"
    message += "/status - Check system status\n"
    message += "/connect - Connect to Arduino\n"
    message += "/disconnect - Disconnect\n"
    message += "/reconnect - Toggle auto-reconnect\n"
    message += "/silence - Acknowledge fall alert\n"
    message += "/help - Show this help\n\n"
    message += "<b>Alert Types:</b>\n"
    message += " <b>Pending Fall:</b> 30-second confirmation window\n"
    message += " <b>Confirmed Fall:</b> No button press detected\n"
    message += " <b>Cancelled Fall:</b> Button pressed in time\n\n"
    message += "<b>Detection Classes:</b>\n"
    message += "The system detects two states:\n"
    message += " <b>Stand</b> - Person is standing upright\n"
    message += " <b>Fall</b> - Person has fallen down\n\n"
    message += "<b>Safety Features:</b>\n"
    message += " Physical button confirmation prevents false alarms\n"
    message += " Emergency contacts get immediate notifications\n"
    message += " Configurable confirmation timeout (default: 30s)\n"
    message += " Alerts can be acknowledged with /silence\n"
    message += " Auto-reconnection for reliability\n\n"
    message += "<b>Troubleshooting:</b>\n"
    message += " Keep Arduino within 5m range for connection\n"
    message += " Only one connection at a time\n"
    message += " Power cycle Arduino if issues persist\n"
    message += " Connection may take up to 60 seconds\n"
    message += " Check battery/power supply\n"
    message += " Ensure Arduino sketch is running\n"
    message += " Test button functionality regularly\n"
    message += " Check BLE advertisement status\n\n"
    message += "<b>Hardware Requirements:</b>\n"
    message += " Arduino Nano 33 BLE Sense\n"
    message += " Push button (normally open)\n"
    message += " 10k pull-up resistor (optional, internal pull-up used)\n"
    message += " Stable power supply\n\n"
    message += " <b>Important:</b> This is a monitoring tool with confirmation system, not a replacement for professional medical alert systems."

    await update.message.reply_text(message, parse_mode='HTML')


async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handle errors"""
    logger.error(f"Update {update} caused error {context.error}")


def check_event_loop():
    """Check if there's already an event loop running"""
    try:
        loop = asyncio.get_running_loop()
        return True
    except RuntimeError:
        return False


async def auto_reconnect_loop():
    """Auto-reconnect loop that runs in background"""
    global auto_reconnect, is_monitoring, ble_client

    while True:
        try:
            await asyncio.sleep(30)  # Check every 30 seconds

            if auto_reconnect and not is_monitoring:
                logger.info("Auto-reconnect: Attempting to reconnect...")
                if await connect_to_device():
                    logger.info("Auto-reconnect: Successfully reconnected")
                else:
                    logger.warning("Auto-reconnect: Failed to reconnect, will try again")

        except Exception as e:
            logger.error(f"Auto-reconnect loop error: {e}")
            await asyncio.sleep(60)  # Wait longer on error


async def connection_monitor():
    """Monitor BLE connection and handle disconnections"""
    global ble_client, is_monitoring, auto_reconnect

    while True:
        try:
            await asyncio.sleep(10)  # Check every 10 seconds

            if is_monitoring and ble_client:
                if not ble_client.is_connected:
                    logger.warning("Connection lost! Device disconnected")
                    is_monitoring = False
                    await send_to_subscribers(" <b>Connection Lost</b>\n Device disconnected unexpectedly")

                    if auto_reconnect:
                        logger.info("Attempting auto-reconnect...")
                        await asyncio.sleep(5)  # Wait a bit before reconnecting

        except Exception as e:
            logger.error(f"Connection monitor error: {e}")
            await asyncio.sleep(30)


async def main():
    """Main function"""
    global application

    print(" Starting Fall Detector Bot with Button Confirmation...")
    print(f"   Bot Token: {'Set' if TOKEN != 'YOUR_TELEGRAM_BOT_TOKEN' else 'NOT SET'}")

    if TOKEN == "YOUR_TELEGRAM_BOT_TOKEN":
        print(" ERROR: Please set your Telegram bot token!")
        return

    # Create application
    application = Application.builder().token(TOKEN).build()

    # Add handlers
    application.add_handler(CommandHandler('start', start_command))
    application.add_handler(CommandHandler('stop', stop_command))
    application.add_handler(CommandHandler('emergency', emergency_command))
    application.add_handler(CommandHandler('status', status_command))
    application.add_handler(CommandHandler('connect', connect_command))
    application.add_handler(CommandHandler('disconnect', disconnect_command))
    application.add_handler(CommandHandler('reconnect', reconnect_command))
    application.add_handler(CommandHandler('silence', silence_command))
    application.add_handler(CommandHandler('help', help_command))
    application.add_error_handler(error_handler)

    print(" Bot initialized!")
    print(" Starting bot...")
    print(" Send /help to your bot for instructions")

    # Initialize and start the application
    try:
        await application.initialize()
        await application.start()
        await application.updater.start_polling(drop_pending_updates=True)

        print(" Bot is running! Press Ctrl+C to stop.")

        # Start background tasks
        auto_reconnect_task = asyncio.create_task(auto_reconnect_loop())
        connection_monitor_task = asyncio.create_task(connection_monitor())

        # Keep the bot running
        try:
            while True:
                await asyncio.sleep(1)
        except KeyboardInterrupt:
            print('\n Received Keyboard Interrupt')

        # Cancel background tasks
        auto_reconnect_task.cancel()
        connection_monitor_task.cancel()

        try:
            await auto_reconnect_task
        except asyncio.CancelledError:
            pass

        try:
            await connection_monitor_task
        except asyncio.CancelledError:
            pass

    except Exception as e:
        print(f" Error starting bot: {e}")

    finally:
        print(" Cleaning up...")
        try:
            # Disconnect BLE device
            await disconnect_device()

            if application.updater.running:
...

This file has been truncated, please download it to see its full contents.

Fall detection Arduino code

Arduino
Arduino code used to detect fall and real time serial monitoring
/* Edge Impulse Fall Detector with BLE Communication and Push Button Confirmation
 * Enhanced version with improved BLE stability, connection handling, and fall confirmation
 * Based on fall_detectorV1.ino with BLE capabilities and push button safety feature
 */

#include <fall-detector_inferencing.h>
#include <Arduino_LSM9DS1.h>
#include <ArduinoBLE.h>

/* Constant defines -------------------------------------------------------- */
#define CONVERT_G_TO_MS2    9.80665f
#define MAX_ACCEPTED_RANGE  2.0f
#define FALL_CONFIRMATION_TIMEOUT 30000  // 30 seconds in milliseconds
#define BUTTON_DEBOUNCE_DELAY 50         // 50ms debounce delay

// BLE Service and Characteristics - Configurable UUIDs
const char* SERVICE_UUID = "19B10000-E8F2-537E-4F6C-D104768A1215";
const char* PREDICTION_CHAR_UUID = "19B10001-E8F2-537E-4F6C-D104768A1215";
const char* CLASSES_CHAR_UUID = "19B10002-E8F2-537E-4F6C-D104768A1215";

BLEService fallDetectorService(SERVICE_UUID);
BLEStringCharacteristic predictionCharacteristic(PREDICTION_CHAR_UUID, BLERead | BLENotify, 512);
BLEStringCharacteristic classesCharacteristic(CLASSES_CHAR_UUID, BLERead, 512);

/* Private variables ------------------------------------------------------- */
static bool debug_nn = false;
static bool ble_connected = false;
static bool ble_initialized = false;
static unsigned long lastConnectionCheck = 0;
static unsigned long lastInference = 0;
static unsigned long lastAdvertiseRestart = 0;
static unsigned long lastBLEStatus = 0;
static const unsigned long INFERENCE_INTERVAL = 2000; // 2 seconds between inferences
static const unsigned long CONNECTION_CHECK_INTERVAL = 1000; // Check connection every second
static const unsigned long ADVERTISE_RESTART_INTERVAL = 30000; // Restart advertising every 30s if not connected
static const unsigned long BLE_STATUS_INTERVAL = 5000; // Print BLE status every 5 seconds

// Hardware pins
const int ALERT_PIN = 6;     // Alert output pin
const int LED_PIN = 23;      // LED indicator pin
const int ALERT_LED = 22;    // Alert LED pin
const int BUTTON_PIN = 4;    // Push button pin

// Connection retry variables
static int connection_failures = 0;
static const int MAX_CONNECTION_FAILURES = 3;

// Fall confirmation state variables
static bool fall_pending_confirmation = false;
static bool fall_confirmed = false;
static bool system_stopped = false;
static unsigned long fall_detection_time = 0;
static unsigned long last_button_press = 0;
static bool last_button_state = HIGH;
static bool button_state = HIGH;

// Function declarations
void runInferenceAndSendResults();
void sendPredictionsInChunks(ei_impulse_result_t& result, String topPrediction, float maxConfidence);
void initializeBLE();
void handleBLEConnection();
void handleFallDetection(ei_impulse_result_t& result);
void handleButtonAndFallConfirmation();
void restartBLEAdvertising();
void onBLEConnected(BLEDevice central);
void onBLEDisconnected(BLEDevice central);

/**
 * @brief Return the sign of the number
 */
float ei_get_sign(float number) {
    return (number >= 0.0) ? 1.0 : -1.0;
}

/**
* @brief      Arduino setup function
*/
void setup()
{
    Serial.begin(115200);
    
    // Wait a bit for serial to stabilize
    delay(3000);
    
    Serial.println("=== Fall Detector with Enhanced BLE and Push Button Confirmation ===");
    Serial.println("Initializing...");
    
    // Initialize hardware pins
    pinMode(ALERT_PIN, OUTPUT);
    pinMode(LED_PIN, OUTPUT);
    pinMode(ALERT_LED, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);  // Enable internal pull-up resistor
    
    digitalWrite(ALERT_PIN, LOW);   // Default state LOW (inactive)
    digitalWrite(LED_PIN, HIGH);    // Default state HIGH (off)
    digitalWrite(ALERT_LED, HIGH);  // Default state HIGH (off)
    
    Serial.println(" Push button configured on pin " + String(BUTTON_PIN));
    Serial.println("   - Press during fall detection to cancel false alarms");
    Serial.println("   - Press after confirmed fall to resume detection");
    
    // Initialize IMU first
    Serial.println(" Initializing IMU...");
    if (!IMU.begin()) {
        ei_printf(" Failed to initialize IMU!\r\n");
        while(1) {
            digitalWrite(LED_PIN, HIGH);
            delay(200);
            digitalWrite(LED_PIN, LOW);
            delay(200);
        }
    }
    else {
        ei_printf(" IMU initialized\r\n");
    }

    // Debug: Print the actual configuration
    ei_printf(" Model configuration:\n");
    ei_printf("   EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME: %d\n", EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
    ei_printf("   EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    ei_printf("   Expected samples per frame: 3 (X, Y, Z axes)\n");
    
    if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) {
        ei_printf(" WARNING: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME is %d, not 3\n", EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
        ei_printf("This might indicate your Edge Impulse model was trained with different input features.\n");
        ei_printf("Please check your Edge Impulse project configuration.\n");
        
        // Don't halt - let's see what happens and provide more info
        ei_printf(" Continuing anyway for debugging...\n");
    }
    
    // Initialize BLE
    Serial.println(" Initializing BLE...");
    initializeBLE();
    
    // Summary of inferencing settings
    ei_printf("Inferencing settings:\n");
    ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
    ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
    
    Serial.println(" Setup complete!");
    Serial.println(" Ready for BLE connections...");
    Serial.println(" Fall detection is active with 30-second confirmation window");
    Serial.println(" Push button safety feature enabled");
    
    // Initialize timing variables
    lastConnectionCheck = millis();
    lastInference = millis();
    lastAdvertiseRestart = millis();
    lastBLEStatus = millis();
}

void initializeBLE() {
    Serial.println(" Starting BLE initialization...");
    
    // Try to initialize BLE multiple times if it fails
    int ble_init_attempts = 3;
    for (int attempt = 0; attempt < ble_init_attempts; attempt++) {
        if (BLE.begin()) {
            Serial.println(" BLE initialized successfully");
            ble_initialized = true;
            break;
        } else {
            Serial.print(" BLE initialization failed, attempt ");
            Serial.print(attempt + 1);
            Serial.print("/");
            Serial.println(ble_init_attempts);
            delay(1000);
        }
    }
    
    if (!ble_initialized) {
        Serial.println(" BLE initialization failed completely! Running without BLE.");
        return;
    }
    
    // Set connection event handlers
    BLE.setEventHandler(BLEConnected, onBLEConnected);
    BLE.setEventHandler(BLEDisconnected, onBLEDisconnected);
    
    // Set BLE parameters for better connectivity and visibility
    BLE.setLocalName("FallDetector");
    BLE.setDeviceName("FallDetector");
    
    // Set connection interval parameters for better stability
    BLE.setConnectionInterval(6, 3200); // 7.5ms to 4000ms
    
    // Set advertising parameters
    BLE.setAdvertisingInterval(160); // 100ms advertising interval
    BLE.setConnectable(true);
    
    Serial.println(" Setting up BLE service and characteristics...");
    
    // Set the advertised service
    BLE.setAdvertisedService(fallDetectorService);
    
    // Add characteristics to service
    fallDetectorService.addCharacteristic(predictionCharacteristic);
    fallDetectorService.addCharacteristic(classesCharacteristic);
    
    // Add the service
    BLE.addService(fallDetectorService);
    
    // Send available classes to characteristics
    String classesJson = "{\"classes\":[";
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        classesJson += "\"" + String(ei_classifier_inferencing_categories[ix]) + "\"";
        if (ix < EI_CLASSIFIER_LABEL_COUNT - 1) classesJson += ",";
    }
    classesJson += "]}";
    
    classesCharacteristic.writeValue(classesJson);
    Serial.println(" Classes written to characteristic: " + classesJson);
    
    // Start advertising
    restartBLEAdvertising();
}

void restartBLEAdvertising() {
    if (!ble_initialized) return;
    
    Serial.println(" Starting/Restarting BLE advertising...");
    
    // Stop advertising first if it's running
    BLE.stopAdvertise();
    delay(100);
    
    // Start advertising
    BLE.advertise();
    Serial.println(" BLE advertising started");
    Serial.println("   Device name: FallDetector");
    Serial.println("   Service UUID: " + String(SERVICE_UUID));
    Serial.println("   Ready for connections!");
    
    lastAdvertiseRestart = millis();
}

void onBLEConnected(BLEDevice central) {
    Serial.print(" BLE Connected! Central: ");
    Serial.println(central.address());
    ble_connected = true;
    connection_failures = 0; // Reset failure counter
    digitalWrite(LED_PIN, LOW); // Turn on LED when connected
}

void onBLEDisconnected(BLEDevice central) {
    Serial.print(" BLE Disconnected! Central: ");
    Serial.println(central.address());
    ble_connected = false;
    if (!fall_pending_confirmation && !fall_confirmed) {
        digitalWrite(ALERT_LED, HIGH); // Turn off LED when disconnected (if no fall alert)
    }
    connection_failures++;
    
    // Restart advertising after disconnection
    Serial.println(" Restarting advertising after disconnection...");
    delay(500); // Small delay before restarting
    restartBLEAdvertising();
}

void handleButtonAndFallConfirmation() {
    unsigned long currentTime = millis();
    
    // Read button with debouncing
    bool reading = digitalRead(BUTTON_PIN);
    
    if (reading != last_button_state) {
        last_button_press = currentTime;
    }
    
    if ((currentTime - last_button_press) > BUTTON_DEBOUNCE_DELAY) {
        if (reading != button_state) {
            button_state = reading;
            
            // Button pressed (assuming active LOW with pull-up)
            if (button_state == LOW) {
                Serial.println(" Button pressed!");
                
                if (fall_pending_confirmation) {
                    // Cancel pending fall
                    Serial.println(" Fall cancelled by user button press");
                    fall_pending_confirmation = false;
                    digitalWrite(ALERT_PIN, LOW);    // Deactivate alert
                    digitalWrite(ALERT_LED, HIGH);   // Turn off alert LED
                    
                    // Restore normal LED state based on BLE connection
                    if (ble_connected) {
                        digitalWrite(LED_PIN, LOW);   // BLE connected
                    } else {
                        digitalWrite(LED_PIN, HIGH);  // BLE not connected
                    }
                    
                } else if (fall_confirmed && system_stopped) {
                    // Reset system after confirmed fall
                    Serial.println(" System reset by user - resuming fall detection");
                    fall_confirmed = false;
                    system_stopped = false;
                    digitalWrite(ALERT_PIN, LOW);    // Deactivate alert
                    digitalWrite(ALERT_LED, HIGH);   // Turn off alert LED
                    
                    // Restore normal LED state based on BLE connection
                    if (ble_connected) {
                        digitalWrite(LED_PIN, LOW);   // BLE connected
                    } else {
                        digitalWrite(LED_PIN, HIGH);  // BLE not connected
                    }
                }
            }
        }
    }
    
    last_button_state = reading;
    
    // Handle fall confirmation timeout
    if (fall_pending_confirmation) {
        if (currentTime - fall_detection_time >= FALL_CONFIRMATION_TIMEOUT) {
            // 30 seconds passed without button press - confirm fall
            Serial.println(" FALL CONFIRMED - No button press within 30 seconds!");
            fall_pending_confirmation = false;
            fall_confirmed = true;
            system_stopped = true;
            
            // Keep alert active and add visual/audio indicators
            digitalWrite(ALERT_PIN, HIGH);   // Keep alert active
            digitalWrite(ALERT_LED, LOW);    // Keep alert LED on
            digitalWrite(LED_PIN, HIGH);      // Override BLE status LED for fall indication
            
            Serial.println(" System stopped - Press button to resume fall detection");
        } else {
            // Show countdown (optional - for debugging)
            static unsigned long last_countdown = 0;
            if (currentTime - last_countdown >= 5000) { // Every 5 seconds
                int remaining = (FALL_CONFIRMATION_TIMEOUT - (currentTime - fall_detection_time)) / 1000;
                Serial.println(" Fall confirmation in " + String(remaining) + " seconds (press button to cancel)");
                last_countdown = currentTime;
            }
        }
    }
}

void loop()
{
    unsigned long currentTime = millis();
    
    // Handle button and fall confirmation logic (ALWAYS active)
    handleButtonAndFallConfirmation();
    
    // Handle BLE connection management
    if (ble_initialized) {
        handleBLEConnection();
    }
    
    // Only run inference if system is not stopped due to confirmed fall
   
        // Run inference at specified intervals (whether connected or not for safety)
    // Run inference at specified intervals (whether connected or not for safety)
    // Only run inference if system is not stopped due to confirmed fall
    if (!system_stopped) {
      // Run inference at specified intervals (whether connected or not for safety)
      if (currentTime - lastInference >= INFERENCE_INTERVAL) {
          runInferenceAndSendResults();
          lastInference = currentTime;
      }
  } else {
      // System is stopped - just send fall state via BLE
      if (currentTime - lastInference >= INFERENCE_INTERVAL) {
          if (ble_initialized && ble_connected && BLE.connected()) {
              String stateJson = "{\"fall_state\":\"timeout\"}";
              predictionCharacteristic.writeValue(stateJson);
              Serial.println(" Sent fall state: timeout");
          }
          lastInference = currentTime;
      }
    
    static unsigned long lastStoppedMessage = 0;
    if (currentTime - lastStoppedMessage >= 10000) { // Every 10 seconds
        Serial.println(" Fall detection STOPPED - Press button to resume");
        lastStoppedMessage = currentTime;
    }
}
    
    // Print BLE status periodically
    if (ble_initialized && (currentTime - lastBLEStatus >= BLE_STATUS_INTERVAL)) {
        Serial.print(" BLE Status - Connected: ");
        Serial.print(ble_connected ? " YES" : " NO");
        if (ble_connected) {
            Serial.print(", Central: ");
            if (BLE.central()) {
                Serial.print(BLE.central().address());
            } else {
                Serial.print("Unknown");
            }
        }
        Serial.print(", Failures: ");
        Serial.print(connection_failures);
        
        // Add fall state to status
        Serial.print(", Fall State: ");
        if (system_stopped) {
            Serial.print(" STOPPED");
        } else if (fall_pending_confirmation) {
            Serial.print(" PENDING");
        } else {
            Serial.print(" NORMAL");
        }
        Serial.println();
        
        lastBLEStatus = currentTime;
    }
    
    // Small delay to prevent overwhelming the processor
    delay(50);
}

void handleBLEConnection() {
    if (!ble_initialized) return;
    
    unsigned long currentTime = millis();
    
    // Check connection status periodically
    if (currentTime - lastConnectionCheck >= CONNECTION_CHECK_INTERVAL) {
        bool currentlyConnected = BLE.connected();
        
        // Update connection status (the event handlers should handle this, but double-check)
        if (currentlyConnected != ble_connected) {
            if (currentlyConnected && !ble_connected) {
                Serial.println(" BLE connection detected (via polling)");
                ble_connected = true;
                if (!fall_pending_confirmation && !fall_confirmed) {
                    digitalWrite(ALERT_LED, LOW);  // Turn on LED if no fall alert
                }
            } else if (!currentlyConnected && ble_connected) {
                Serial.println(" BLE disconnection detected (via polling)");
                ble_connected = false;
                if (!fall_pending_confirmation && !fall_confirmed) {
                    digitalWrite(ALERT_LED, HIGH); // Turn off LED if no fall alert
                }
            }
        }
        
        // Restart advertising if not connected and enough time has passed
        if (!ble_connected && (currentTime - lastAdvertiseRestart >= ADVERTISE_RESTART_INTERVAL)) {
            Serial.println(" Restarting advertising (periodic maintenance)");
            restartBLEAdvertising();
        }
        
        // If too many connection failures, try to reinitialize BLE
        if (connection_failures >= MAX_CONNECTION_FAILURES) {
            Serial.println(" Too many connection failures, reinitializing BLE...");
            BLE.end();
            delay(1000);
            initializeBLE();
            connection_failures = 0;
        }
        
        lastConnectionCheck = currentTime;
    }
    
    // Poll BLE events
    BLE.poll();
}

void runInferenceAndSendResults() {
    ei_printf(" Sampling...\n");

    // Allocate a buffer here for the values we'll read from the IMU
    float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };

    // Calculate how many samples we need based on the model configuration
    int samples_per_frame = EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME;
    int total_samples = EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE / samples_per_frame;
    
    ei_printf("   Collecting %d samples with %d features each\n", total_samples, samples_per_frame);

    for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += samples_per_frame) {
        // Determine the next tick (and then sleep later)
        uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);

        float x, y, z;
        IMU.readAcceleration(x, y, z);

        // Handle different input configurations
        if (samples_per_frame >= 3) {
            // Standard 3-axis configuration
            buffer[ix + 0] = x;
            buffer[ix + 1] = y;
            buffer[ix + 2] = z;
            
            // Apply range limiting and conversion
            for (int i = 0; i < 3; i++) {
                if (fabs(buffer[ix + i]) > MAX_ACCEPTED_RANGE) {
                    buffer[ix + i] = ei_get_sign(buffer[ix + i]) * MAX_ACCEPTED_RANGE;
                }
                buffer[ix + i] *= CONVERT_G_TO_MS2;
            }
            
            // Fill any additional features with derived values
            if (samples_per_frame > 3) {
                // Add magnitude as 4th feature if needed
                if (samples_per_frame >= 4) {
                    buffer[ix + 3] = sqrt(x*x + y*y + z*z) * CONVERT_G_TO_MS2;
                }
                // Add more derived features if needed
                for (int i = 4; i < samples_per_frame; i++) {
                    buffer[ix + i] = 0.0f; // Zero-pad additional features
                }
            }
        } else {
            // Handle unusual configurations
            ei_printf(" Unusual samples_per_frame: %d\n", samples_per_frame);
            for (int i = 0; i < samples_per_frame && i < 3; i++) {
                float val = (i == 0) ? x : (i == 1) ? y : z;
                if (fabs(val) > MAX_ACCEPTED_RANGE) {
                    val = ei_get_sign(val) * MAX_ACCEPTED_RANGE;
                }
                buffer[ix + i] = val * CONVERT_G_TO_MS2;
            }
        }

        delayMicroseconds(next_tick - micros());
    }

    // Turn the raw buffer in a signal which we can the classify
    signal_t signal;
    int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
    if (err != 0) {
        ei_printf(" Failed to create signal from buffer (%d)\n", err);
        return;
    }

    // Run the classifier
    ei_impulse_result_t result = { 0 };

    err = run_classifier(&signal, &result, debug_nn);
    if (err != EI_IMPULSE_OK) {
        ei_printf(" ERR: Failed to run classifier (%d)\n", err);
        return;
    }

    // Handle fall detection with push button confirmation logic
    handleFallDetection(result);

    // Find the top prediction
    String topPrediction = "unknown";
    float maxConfidence = 0.0;
    
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        if (result.classification[ix].value > maxConfidence) {
            maxConfidence = result.classification[ix].value;
            topPrediction = String(result.classification[ix].label);
        }
    }
    
    // Print predictions to serial (always)
    ei_printf(" Predictions ");
    ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
        result.timing.dsp, result.timing.classification, result.timing.anomaly);
    ei_printf(": \n");
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        ei_printf("    %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
    }
    
#if EI_CLASSIFIER_HAS_ANOMALY == 1
    ei_printf("    anomaly score: %.3f\n", result.anomaly);
#endif

    // Send via BLE if connected and confidence is above threshold
    if (ble_initialized && ble_connected && maxConfidence > 0.1) {
        Serial.print(" Sending to BLE: ");
        Serial.print(topPrediction);
        Serial.print(" (");
        Serial.print(maxConfidence * 100, 2);
        Serial.println("%)");
        sendPredictionsInChunks(result, topPrediction, maxConfidence);
    } else if (ble_initialized && !ble_connected) {
        Serial.println(" Not connected to BLE - skipping transmission");
    } else if (!ble_initialized) {
        Serial.println(" BLE not initialized - running in standalone mode");
    }
}

void handleFallDetection(ei_impulse_result_t& result) {
    // Skip fall detection if system is stopped or fall is pending confirmation
    if (system_stopped || fall_pending_confirmation) {
        return;
    }
    
    bool fallDetected = false;
    float fallConfidence = 0.0;
    
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        if (strcmp(result.classification[ix].label, "Fall") == 0) {
            fallConfidence = result.classification[ix].value;
            
            if (fallConfidence > 0.99) {
                // Start fall confirmation process instead of immediate alert
                fall_pending_confirmation = true;
                fall_detection_time = millis();
                
                // Activate temporary alert during confirmation period
                digitalWrite(ALERT_PIN, HIGH);   // Activate alert
                digitalWrite(ALERT_LED, LOW);    // Turn on alert LED
                digitalWrite(LED_PIN, HIGH);      // Override BLE status for fall indication
                
                Serial.println(" POTENTIAL FALL DETECTED!");
                Serial.println("   Confidence: " + String(fallConfidence * 100, 2) + "%");
                Serial.println("   Press button within 30 seconds to cancel");
                Serial.println("   Or fall will be confirmed automatically");
                
                fallDetected = true;
            } else if (fallConfidence > 0.5) {
                ei_printf(" Potential fall detected but below threshold (%.2f%%)\n", fallConfidence * 100);
            }
            break;
        }
    }
    
    // If no significant fall detected, make sure alert is off (unless in confirmation mode)
    if (!fallDetected && !fall_pending_confirmation && !fall_confirmed) {
        digitalWrite(ALERT_PIN, LOW);   // Deactivate alert
        digitalWrite(ALERT_LED, HIGH);  // Turn off alert LED
        
        
        // Only control LED for BLE status if no fall alert
        if (ble_connected) {
            digitalWrite(LED_PIN, LOW);   // BLE connected
        } else {
            digitalWrite(LED_PIN, HIGH);  // BLE not connected
        }
    }
}

void sendPredictionsInChunks(ei_impulse_result_t& result, String topPrediction, float maxConfidence) {
    if (!ble_initialized || !ble_connected) {
        Serial.println(" Not connected - skipping BLE transmission");
        return;
    }
    
    // Verify we still have a connection before sending
    if (!BLE.connected()) {
        Serial.println(" BLE connection lost during send preparation");
        ble_connected = false;
        return;
    }
    
    // Build JSON response
    String jsonResponse = "{";
    jsonResponse += "\"top\":\"" + topPrediction + "\",";
    jsonResponse += "\"conf\":" + String(maxConfidence, 3) + ",";
    
    // Add fall state information
    jsonResponse += "\"fall_state\":\"";
    if (system_stopped) {
        jsonResponse += "timeout";
    } else if (fall_pending_confirmation) {
        jsonResponse += "pending";
    } else {
        jsonResponse += "normal";
    }
    jsonResponse += "\",";
    
    jsonResponse += "\"predictions\":[";
    
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        jsonResponse += "{\"l\":\"" + String(result.classification[ix].label) + "\",";
        jsonResponse += "\"c\":" + String(result.classification[ix].value, 3) + "}";
        if (ix < EI_CLASSIFIER_LABEL_COUNT - 1) jsonResponse += ",";
    }
    jsonResponse += "]";
    
    // Add anomaly score if available
    #if EI_CLASSIFIER_HAS_ANOMALY == 1
    jsonResponse += ",\"anom\":" + String(result.anomaly, 3);
    #endif
    
    jsonResponse += "}";
    
    Serial.println(" Preparing to send: " + jsonResponse);
    
    // Send in chunks if needed (BLE has size limitations)
    const int chunkSize = 180; // Conservative chunk size for better reliability
    int totalLength = jsonResponse.length();
    
    if (totalLength <= chunkSize) {
        // Send as single message with error checking
        if (BLE.connected()) {
            bool writeSuccess = predictionCharacteristic.writeValue(jsonResponse);
            if (writeSuccess) {
                Serial.println(" Sent complete message (" + String(totalLength) + " bytes)");
            } else {
                Serial.println(" Failed to write to BLE characteristic");
                ble_connected = false;
            }
        } else {
            Serial.println(" Connection lost before sending");
            ble_connected = false;
        }
    } else {
        // Send in chunks
        int totalChunks = (totalLength + chunkSize - 1) / chunkSize;
        Serial.println(" Sending in " + String(totalChunks) + " chunks...");
        
        bool send_success = true;
        for (int i = 0; i < totalChunks && send_success; i++) {
            // Check connection before each chunk
            if (!BLE.connected()) {
                Serial.println(" Connection lost during chunked transmission");
                ble_connected = false;
                send_success = false;
                break;
            }
            
            int startPos = i * chunkSize;
            int endPos = min(startPos + chunkSize, totalLength);
            String chunk = jsonResponse.substring(startPos, endPos);
            
            // Format: chunkIndex:totalChunks:data
            String chunkMessage = String(i) + ":" + String(totalChunks) + ":" + chunk;
            
            bool writeSuccess = predictionCharacteristic.writeValue(chunkMessage);
            if (writeSuccess) {
                Serial.print(" Sent chunk ");
                Serial.print(i + 1);
                Serial.print("/");
                Serial.print(totalChunks);
                Serial.print(" (");
                Serial.print(chunk.length());
                Serial.println(" bytes)");
                
                // Small delay between chunks for reliability
                delay(100);
            } else {
                Serial.println(" Failed to send chunk " + String(i + 1));
                ble_connected = false;
                send_success = false;
                break;
            }
        }
        
        if (send_success) {
            Serial.println(" All chunks sent successfully");
        } else {
            Serial.println(" Chunked transmission failed");
        }
    }
}

Credits

sooraj
1 project • 0 followers
Sryshnav CM
1 project • 0 followers
Mohammed_Shibin
1 project • 0 followers
Sreya. V
1 project • 1 follower

Comments