diff --git a/vitallink/link-start.sh b/vitallink/link-start.sh new file mode 100755 index 0000000..eeaeedb --- /dev/null +++ b/vitallink/link-start.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$PROJECT_ROOT" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}" +cat <<"EOF" +╔══════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ██╗ ██╗██╗████████╗ █████╗ ██╗ ██╗ ██╗███╗ ██╗██╗ ██╗ ║ +║ ██║ ██║██║╚══██╔══╝██╔══██╗██║ ██║ ██║████╗ ██║██║ ██╔╝ ║ +║ ██║ ██║██║ ██║ ███████║██║ ██║ ██║██╔██╗ ██║█████╔╝ ║ +║ ╚██╗ ██╔╝██║ ██║ ██╔══██║██║ ██║ ██║██║╚██╗██║██╔═██╗ ║ +║ ╚████╔╝ ██║ ██║ ██║ ██║███████╗███████╗██║██║ ╚████║██║ ██╗ ║ +║ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ║ +║ ║ +║ Complete System Startup ║ +║ ║ +╚══════════════════════════════════════════════════════════════════════════╝ +EOF +echo -e "${NC}" + +echo -e "${YELLOW}Starting VitalLink Complete System...${NC}\n" + +# Create logs directory +mkdir -p logs + +# Activate Python virtual environment +echo -e "${BLUE}[1/5]${NC} Activating Python environment..." +source .venv/bin/activate + +# Start Backend +echo -e "${BLUE}[2/5]${NC} Starting Backend API..." +python backend/server.py >logs/backend.log 2>&1 & +BACKEND_PID=$! +echo $BACKEND_PID >logs/backend.pid +echo -e " ${GREEN}✓${NC} Backend started (PID: $BACKEND_PID)" +sleep 3 + +# Start Wristband System +echo -e "${BLUE}[3/5]${NC} Starting Wristband Management System..." +python simulator/main_runner.py >logs/wristbands.log 2>&1 & +WRISTBAND_PID=$! +echo $WRISTBAND_PID >logs/wristbands.pid +echo -e " ${GREEN}✓${NC} Wristband system started (PID: $WRISTBAND_PID)" +sleep 2 + +# Start Dashboard Frontend +echo -e "${BLUE}[4/5]${NC} Starting Staff Dashboard..." +cd frontend/dashboard +npm run dev >../../logs/dashboard.log 2>&1 & +DASHBOARD_PID=$! +echo $DASHBOARD_PID >../../logs/dashboard.pid +cd ../.. +echo -e " ${GREEN}✓${NC} Dashboard started (PID: $DASHBOARD_PID)" +sleep 2 + +# Start Kiosk Frontend +echo -e "${BLUE}[5/5]${NC} Starting Check-in Kiosk..." +cd frontend/kiosk +npm run dev >../../logs/kiosk.log 2>&1 & +KIOSK_PID=$! +echo $KIOSK_PID >../../logs/kiosk.pid +cd ../.. +echo -e " ${GREEN}✓${NC} Kiosk started (PID: $KIOSK_PID)" + +echo "" +echo -e "${GREEN}═══════════════════════════════════════════════════════════════════════${NC}" +echo -e "${GREEN} ✅ VitalLink System Running! ${NC}" +echo -e "${GREEN}═══════════════════════════════════════════════════════════════════════${NC}" +echo "" +echo -e "${YELLOW}📊 Access Points:${NC}" +echo -e " ${BLUE}•${NC} Backend API: http://localhost:8000" +echo -e " ${BLUE}•${NC} API Documentation: http://localhost:8000/docs" +echo -e " ${BLUE}•${NC} Staff Dashboard: http://localhost:5173" +echo -e " ${BLUE}•${NC} Check-in Kiosk: http://localhost:5174" +echo "" +echo -e "${YELLOW}📝 View Logs:${NC}" +echo -e " ${BLUE}•${NC} Backend: tail -f logs/backend.log" +echo -e " ${BLUE}•${NC} Wristbands: tail -f logs/wristbands.log" +echo -e " ${BLUE}•${NC} Dashboard: tail -f logs/dashboard.log" +echo -e " ${BLUE}•${NC} Kiosk: tail -f logs/kiosk.log" +echo "" +echo -e "${YELLOW}🔧 System Features:${NC}" +echo -e " ${GREEN}✓${NC} Auto-assigns wristbands when patients check in" +echo -e " ${GREEN}✓${NC} Prefers real wristbands over simulated" +echo -e " ${GREEN}✓${NC} Creates emergency simulated bands if needed" +echo -e " ${GREEN}✓${NC} Real-time monitoring and updates" +echo "" +echo -e "${YELLOW}🛑 Stop System:${NC}" +echo -e " ${BLUE}•${NC} Run: ./stop_everything.sh" +echo -e " ${BLUE}•${NC} Or press Ctrl+C (will stop all services)" +echo "" +echo -e "${GREEN}═══════════════════════════════════════════════════════════════════════${NC}" +echo "" + +# Save all PIDs for easy cleanup +cat >logs/all_pids.txt < dashboard@0.0.0 dev +> vite + + + VITE v7.1.10 ready in 100 ms + + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose diff --git a/vitallink/logs/dashboard.pid b/vitallink/logs/dashboard.pid new file mode 100644 index 0000000..5185dbd --- /dev/null +++ b/vitallink/logs/dashboard.pid @@ -0,0 +1 @@ +103478 diff --git a/vitallink/logs/kiosk.log b/vitallink/logs/kiosk.log new file mode 100644 index 0000000..89aaf3a --- /dev/null +++ b/vitallink/logs/kiosk.log @@ -0,0 +1,10 @@ + +> kiosk@0.0.0 dev +> vite + +Port 5173 is in use, trying another one... + + VITE v7.1.10 ready in 103 ms + + ➜ Local: http://localhost:5174/ + ➜ Network: use --host to expose diff --git a/vitallink/logs/kiosk.pid b/vitallink/logs/kiosk.pid new file mode 100644 index 0000000..09ae417 --- /dev/null +++ b/vitallink/logs/kiosk.pid @@ -0,0 +1 @@ +103512 diff --git a/vitallink/logs/wristbands.log b/vitallink/logs/wristbands.log new file mode 100644 index 0000000..1aff406 --- /dev/null +++ b/vitallink/logs/wristbands.log @@ -0,0 +1,25 @@ +⚠️ Bleak not installed. Real wristbands disabled. Install with: pip install bleak +Creating default config at wristband_config.yaml +✓ Loaded configuration from wristband_config.yaml + +================================================================================ +VitalLink System Initialization +================================================================================ + +✓ Backend is running at http://localhost:8000 +Traceback (most recent call last): + File "/home/mai/documents/school/capstone/vitallink-BS/vitallink/simulator/main_runner.py", line 282, in + asyncio.run(system.run()) + File "/home/mai/.local/share/uv/python/cpython-3.9.20-linux-x86_64-gnu/lib/python3.9/asyncio/runners.py", line 44, in run + return loop.run_until_complete(main) + File "/home/mai/.local/share/uv/python/cpython-3.9.20-linux-x86_64-gnu/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete + return future.result() + File "/home/mai/documents/school/capstone/vitallink-BS/vitallink/simulator/main_runner.py", line 153, in run + await self.initialize() + File "/home/mai/documents/school/capstone/vitallink-BS/vitallink/simulator/main_runner.py", line 52, in initialize + self.manager.add_simulated_band( + File "/home/mai/documents/school/capstone/vitallink-BS/vitallink/simulator/wristband_manager.py", line 324, in add_simulated_band + band = SimulatedWristband(band_id, profile) + File "/home/mai/documents/school/capstone/vitallink-BS/vitallink/simulator/wristband_manager.py", line 243, in __init__ + from simulator.wristband_simulator import PATIENT_PROFILES, WristbandSimulator +ModuleNotFoundError: No module named 'simulator' diff --git a/vitallink/logs/wristbands.pid b/vitallink/logs/wristbands.pid new file mode 100644 index 0000000..a9f66c2 --- /dev/null +++ b/vitallink/logs/wristbands.pid @@ -0,0 +1 @@ +103468 diff --git a/vitallink/simulator/__pycache__/config_system.cpython-39.pyc b/vitallink/simulator/__pycache__/config_system.cpython-39.pyc new file mode 100644 index 0000000..5090c61 Binary files /dev/null and b/vitallink/simulator/__pycache__/config_system.cpython-39.pyc differ diff --git a/vitallink/simulator/__pycache__/wristband_manager.cpython-39.pyc b/vitallink/simulator/__pycache__/wristband_manager.cpython-39.pyc new file mode 100644 index 0000000..4fc8e6f Binary files /dev/null and b/vitallink/simulator/__pycache__/wristband_manager.cpython-39.pyc differ diff --git a/vitallink/simulator/config_system.py b/vitallink/simulator/config_system.py index 845a0d5..6597eb4 100644 --- a/vitallink/simulator/config_system.py +++ b/vitallink/simulator/config_system.py @@ -167,7 +167,7 @@ def cli_inventory(): print("=" * 80) print("\nSimulated Wristbands:") - simulated = config.get_simulated_bands() + simulated = config.get_simulated_bands() or [] # Add "or []" if simulated: for band in simulated: print(f" 🟢 {band['band_id']:20} | Profile: {band['profile']}") @@ -175,7 +175,7 @@ def cli_inventory(): print(" (none configured)") print("\nReal Wristbands (Hardware):") - real = config.get_real_bands() + real = config.get_real_bands() or [] # Add "or []" if real: for band in real: print(f" 🔵 {band['band_id']:20} | BLE: {band['ble_address']}") diff --git a/vitallink/simulator/main_runner.py b/vitallink/simulator/main_runner.py index df109dd..e0486eb 100644 --- a/vitallink/simulator/main_runner.py +++ b/vitallink/simulator/main_runner.py @@ -1,12 +1,14 @@ """ VitalLink Main System Runner Runs the complete system with real and/or simulated wristbands +Automatically assigns bands when patients check in via kiosk """ import asyncio import aiohttp from wristband_manager import WristbandManager from config_system import WristbandConfig +import sys # ============================================================================ # MAIN SYSTEM @@ -21,6 +23,7 @@ class VitalLinkSystem: self.config = WristbandConfig() self.backend_url = self.config.get("backend_url", "http://localhost:8000") self.running = False + self.monitoring_task = None async def initialize(self): """Initialize the system""" @@ -29,7 +32,9 @@ class VitalLinkSystem: print("=" * 80 + "\n") # Check backend availability - await self.check_backend() + backend_ok = await self.check_backend() + if not backend_ok: + print("\n⚠️ Warning: Backend not running. System will wait for backend...") # Scan for real wristbands if configured if self.config.get("auto_scan_ble", False): @@ -37,13 +42,13 @@ class VitalLinkSystem: await self.manager.scan_for_real_bands(timeout) # Load configured real wristbands - for band_config in self.config.get_real_bands(): + for band_config in self.config.get_real_bands() or []: self.manager.add_real_band( band_config["band_id"], band_config["ble_address"] ) # Load configured simulated wristbands - for band_config in self.config.get_simulated_bands(): + for band_config in self.config.get_simulated_bands() or []: self.manager.add_simulated_band( band_config["band_id"], band_config.get("profile", "stable") ) @@ -55,89 +60,112 @@ class VitalLinkSystem: """Check if backend is running""" try: async with aiohttp.ClientSession() as session: - async with session.get(f"{self.backend_url}/") as resp: + async with session.get( + f"{self.backend_url}/", timeout=aiohttp.ClientTimeout(total=3) + ) as resp: if resp.status == 200: print(f"✓ Backend is running at {self.backend_url}") return True except Exception as e: print(f"❌ Backend not reachable at {self.backend_url}") - print(f" Error: {e}") - print("\n⚠️ Start backend with: python backend/server.py") return False - async def auto_checkin_and_assign(self): - """Automatically check in patients and assign available bands""" + return False - # Mock patients for demo - demo_patients = [ - { - "firstName": "John", - "lastName": "Smith", - "dob": "1985-03-15", - "symptoms": ["Chest Pain"], - "severity": "mild", - }, - { - "firstName": "Sarah", - "lastName": "Johnson", - "dob": "1990-07-22", - "symptoms": ["Fever", "Difficulty Breathing"], - "severity": "moderate", - }, - { - "firstName": "Michael", - "lastName": "Chen", - "dob": "1978-11-05", - "symptoms": ["Severe Headache"], - "severity": "severe", - }, - ] - - print("\nAuto check-in patients...") + async def monitor_new_patients(self): + """Monitor backend for new patient check-ins and auto-assign bands""" + print("\n🔍 Monitoring for new patient check-ins...") + known_patients = set() prefer_real = self.config.get("prefer_real_bands", False) - for patient_data in demo_patients: + while self.running: try: - # Check in patient via API async with aiohttp.ClientSession() as session: - async with session.post( - f"{self.backend_url}/api/checkin", json=patient_data - ) as resp: + async with session.get(f"{self.backend_url}/api/queue") as resp: if resp.status == 200: - data = await resp.json() - patient_id = data["patient_id"] - assigned_band_id = data["band_id"] + queue = await resp.json() - print( - f"✓ {patient_data['firstName']} {patient_data['lastName']} → {patient_id}" - ) + for patient in queue: + patient_id = patient["patient_id"] - # Find and assign a physical/simulated band - band = self.manager.assign_band( - patient_id, prefer_real=prefer_real - ) + # New patient detected + if patient_id not in known_patients: + known_patients.add(patient_id) - if band: - # Start monitoring - await self.manager.start_monitoring(band.band_id) + # Check if already has a band assigned and monitoring + has_active_band = any( + b.patient_id == patient_id + and b.band_id in self.manager.active_monitoring + for b in self.manager.inventory.values() + ) + + if not has_active_band: + print( + f"\n🆕 New patient detected: {patient_id} ({patient['name']})" + ) + + # Try to assign a band + band = self.manager.assign_band( + patient_id, prefer_real=prefer_real + ) + + if band: + print( + f" ✓ Assigned {band.band_id} ({band.type.value})" + ) + + # Start monitoring + await self.manager.start_monitoring( + band.band_id + ) + else: + # No bands available - create a new simulated one on the fly + print( + f" ⚠️ No bands available, creating emergency simulated band..." + ) + + emergency_band_id = f"VitalLink-EMRG{len(self.manager.inventory):02d}" + band = self.manager.add_simulated_band( + emergency_band_id, "stable" + ) + band.assign_to_patient(patient_id) + + print( + f" ✓ Created and assigned {emergency_band_id}" + ) + + await self.manager.start_monitoring( + band.band_id + ) except Exception as e: - print(f"❌ Failed to check in {patient_data['firstName']}: {e}") + # Silently continue if backend temporarily unavailable + pass - print() + # Check every 2 seconds + await asyncio.sleep(2) async def run(self): """Run the main system""" self.running = True await self.initialize() - await self.auto_checkin_and_assign() - print("=" * 80) + print("\n" + "=" * 80) print("VitalLink System Running") print("=" * 80) - print("\nMonitoring patients... Press Ctrl+C to stop\n") + print("\n✓ Monitoring for new patients from kiosk check-ins") + print( + "✓ Auto-assigning wristbands (prefer real: {})".format( + self.config.get("prefer_real_bands", False) + ) + ) + print("\nPress Ctrl+C to stop\n") + print("=" * 80 + "\n") + + # Start monitoring for new patients + self.monitoring_task = asyncio.create_task(self.monitor_new_patients()) try: # Keep running until interrupted @@ -146,20 +174,29 @@ class VitalLinkSystem: # Periodic status update status = self.manager.get_status() + available = status["status_breakdown"].get("available", 0) + print( - f"[{asyncio.get_event_loop().time():.0f}s] Active: {status['active_monitoring']} | " - f"Available: {status['status_breakdown']['available']}" + f"[Status] Active: {status['active_monitoring']} monitoring | " + f"Available: {available} bands | " + f"Real: {status['real_bands']} | " + f"Sim: {status['simulated_bands']}" ) except KeyboardInterrupt: - print("\n\nShutting down...") + print("\n\n⚠️ Shutting down...") await self.shutdown() async def shutdown(self): """Clean shutdown""" self.running = False + # Cancel monitoring task + if self.monitoring_task: + self.monitoring_task.cancel() + # Stop all monitoring + print("Stopping all wristband monitoring...") for band_id in list(self.manager.active_monitoring.keys()): await self.manager.stop_monitoring(band_id) @@ -185,7 +222,7 @@ async def interactive_mode(): print("2. Scan for real wristbands") print("3. Assign band to patient") print("4. Release band") - print("5. Start auto-demo") + print("5. Start auto-monitoring mode") print("6. Exit") choice = input("\nSelect option: ") @@ -236,9 +273,13 @@ if __name__ == "__main__": args = parser.parse_args() - if args.interactive: - asyncio.run(interactive_mode()) - else: - # Normal automatic mode - system = VitalLinkSystem() - asyncio.run(system.run()) + try: + if args.interactive: + asyncio.run(interactive_mode()) + else: + # Normal automatic mode + system = VitalLinkSystem() + asyncio.run(system.run()) + except KeyboardInterrupt: + print("\n\nExiting...") + sys.exit(0) diff --git a/vitallink/simulator/wristband_manager.py b/vitallink/simulator/wristband_manager.py index 07d9384..b969f1d 100644 --- a/vitallink/simulator/wristband_manager.py +++ b/vitallink/simulator/wristband_manager.py @@ -240,7 +240,7 @@ class SimulatedWristband(BaseWristband): self.running = False # Import simulator profiles - from simulator.wristband_simulator import PATIENT_PROFILES, WristbandSimulator + from wristband_simulator import PATIENT_PROFILES, WristbandSimulator self.simulator = WristbandSimulator( band_id, PATIENT_PROFILES.get(profile, PATIENT_PROFILES["stable"]), None diff --git a/vitallink/start.sh b/vitallink/start.sh deleted file mode 100755 index 109b646..0000000 --- a/vitallink/start.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$PROJECT_ROOT" - -# Activate venv (bash way) -source venv/bin/activate 2>/dev/null || source .venv/bin/activate 2>/dev/null - -echo "╔═══════════════════════════════════════════════════════════════════════╗" -echo "║ Starting VitalLink System ║" -echo "╚═══════════════════════════════════════════════════════════════════════╝" -echo "" - -mkdir -p logs - -echo "Starting backend server..." -python backend/server.py > logs/backend.log 2>&1 & -echo $! > logs/backend.pid -echo "✓ Backend started (PID: $(cat logs/backend.pid))" - -sleep 3 - -echo "Starting wristband simulator..." -python simulator/wristband_simulator.py > logs/simulator.log 2>&1 & -echo $! > logs/simulator.pid -echo "✓ Simulator started (PID: $(cat logs/simulator.pid))" - -echo "" -echo "═══════════════════════════════════════════════════════════════════════" -echo "✅ VitalLink System Running!" -echo "═══════════════════════════════════════════════════════════════════════" -echo "" -echo "📊 Access Points:" -echo " • API Docs: http://localhost:8000/docs" -echo " • API Stats: http://localhost:8000/api/stats" -echo " • WebSocket: ws://localhost:8000/ws" -echo " • Staff Dashboard: file://$PROJECT_ROOT/frontend/dashboard/index.html" -echo " • Check-in Kiosk: file://$PROJECT_ROOT/frontend/kiosk/index.html" -echo "" -echo "📝 View Logs:" -echo " • Backend: tail -f logs/backend.log" -echo " • Simulator: tail -f logs/simulator.log" -echo "" -echo "🛑 Stop System:" -echo " • Run: ./stop.sh" -echo "" -echo "═══════════════════════════════════════════════════════════════════════" diff --git a/vitallink/stop.sh b/vitallink/stop.sh deleted file mode 100755 index a828f11..0000000 --- a/vitallink/stop.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$PROJECT_ROOT" - -echo "Stopping VitalLink system..." - -if [ -f logs/backend.pid ]; then - kill $(cat logs/backend.pid) 2>/dev/null && echo "✓ Backend stopped" || echo "Backend not running" - rm -f logs/backend.pid -fi - -if [ -f logs/simulator.pid ]; then - kill $(cat logs/simulator.pid) 2>/dev/null && echo "✓ Simulator stopped" || echo "Simulator not running" - rm -f logs/simulator.pid -fi - -echo "✓ VitalLink system stopped" diff --git a/vitallink/stop_everything.sh b/vitallink/stop_everything.sh new file mode 100755 index 0000000..a554c6d --- /dev/null +++ b/vitallink/stop_everything.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$PROJECT_ROOT" + +echo "Stopping VitalLink Complete System..." +echo "" + +# Function to stop a service +stop_service() { + local name=$1 + local pidfile="logs/${name}.pid" + + if [ -f "$pidfile" ]; then + PID=$(cat "$pidfile") + if kill -0 $PID 2>/dev/null; then + kill $PID 2>/dev/null + echo "✓ Stopped $name (PID: $PID)" + else + echo " $name already stopped" + fi + rm -f "$pidfile" + fi +} + +# Stop all services +stop_service "backend" +stop_service "wristbands" +stop_service "dashboard" +stop_service "kiosk" + +# Cleanup +rm -f logs/all_pids.txt + +echo "" +echo "✓ VitalLink system stopped" diff --git a/vitallink/test.sh b/vitallink/test.sh deleted file mode 100755 index 21acccc..0000000 --- a/vitallink/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -cat >test.sh <<'TESTEOF' -#!/bin/bash - -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$PROJECT_ROOT" -source venv/bin/activate 2>/dev/null || source .venv/bin/activate 2>/dev/null - -echo "Running VitalLink Test Suite..." -echo "" -python tests/test_suite.py -TESTEOF - -chmod +x test.sh diff --git a/vitallink/wristband_config.yaml b/vitallink/wristband_config.yaml new file mode 100644 index 0000000..12332bc --- /dev/null +++ b/vitallink/wristband_config.yaml @@ -0,0 +1,40 @@ + +# VitalLink Wristband Configuration +# Edit this file to manage your wristband inventory + +# Backend API URL +backend_url: "http://localhost:8000" + +# Auto-scan for real wristbands on startup +auto_scan_ble: false +scan_timeout: 10.0 + +# Simulated Wristbands +# Add as many as you need for testing +simulated_bands: + - band_id: "VitalLink-SIM1" + profile: "stable" + + - band_id: "VitalLink-SIM2" + profile: "mild_anxiety" + + - band_id: "VitalLink-SIM3" + profile: "deteriorating" + +# Real Wristbands (Hardware) +# Add BLE addresses of your physical wristbands +# You can find these by running: python -m wristband_manager --scan +real_bands: + # Example (uncomment and edit when you have real hardware): + # - band_id: "VitalLink-A3B2" + # ble_address: "D7:91:3F:9A:12:34" + # + # - band_id: "VitalLink-7B42" + # ble_address: "E1:84:7B:42:56:78" + +# Default preference when assigning bands +prefer_real_bands: false # Set to true to use real bands first + +# Patient profiles for simulated bands +# Options: stable, mild_anxiety, deteriorating, critical, sepsis +default_profile: "stable"