diff --git a/vitallink/backend/server.py b/vitallink/backend/server.py
index 2ddbf0e..0751b7b 100644
--- a/vitallink/backend/server.py
+++ b/vitallink/backend/server.py
@@ -14,7 +14,7 @@ import json
from collections import defaultdict
# ============================================================================
-# LIFESPAN MANAGEMENT (Modern FastAPI Way)
+# LIFESPAN MANAGEMENT
# ============================================================================
@@ -38,10 +38,9 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="VitalLink API", version="1.0.0", lifespan=lifespan)
-# CORS middleware for frontend
app.add_middleware(
CORSMiddleware,
- allow_origins=["*"], # In production, specify your frontend domain
+ allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -100,7 +99,7 @@ class QueuePosition(BaseModel):
# ============================================================================
-# IN-MEMORY STORAGE (Replace with database in production)
+# IN-MEMORY STORAGE
# ============================================================================
patients_db: Dict[str, Patient] = {}
@@ -110,58 +109,44 @@ available_bands = [
]
active_websockets: List[WebSocket] = []
+# Wristband details cache
+wristband_details_cache = {}
+
# ============================================================================
# PRIORITY ALGORITHM
# ============================================================================
def calculate_priority_score(patient: Patient) -> float:
- """
- Calculate dynamic priority score for queue ordering
- Higher score = higher priority
-
- Factors:
- - Tier (Emergency=100, Alert=50, Normal=0)
- - Vital sign trends (worsening = higher)
- - Wait time (exponential increase after threshold)
- - Initial severity
- """
score = 0.0
- # Tier contribution (largest factor)
tier_scores = {"EMERGENCY": 100, "ALERT": 50, "NORMAL": 0}
score += tier_scores.get(patient.current_tier, 0)
- # Wait time contribution (increases exponentially after 30 min)
wait_minutes = (datetime.now() - patient.check_in_time).total_seconds() / 60
if wait_minutes > 30:
- score += (wait_minutes - 30) * 0.5 # 0.5 points per minute over 30
+ score += (wait_minutes - 30) * 0.5
elif wait_minutes > 60:
- score += (wait_minutes - 60) * 1.0 # Accelerate after 1 hour
+ score += (wait_minutes - 60) * 1.0
- # Initial severity contribution
severity_scores = {"severe": 20, "moderate": 10, "mild": 5}
score += severity_scores.get(patient.severity, 0)
- # Vital signs contribution (if available)
if patient.last_vitals:
hr = patient.last_vitals.get("hr_bpm", 75)
spo2 = patient.last_vitals.get("spo2", 98)
temp = patient.last_vitals.get("temp_c", 37.0)
- # Abnormal HR
if hr > 110 or hr < 50:
score += 10
if hr > 140 or hr < 40:
score += 30
- # Low SpO2 (critical)
if spo2 < 92:
score += 15
if spo2 < 88:
score += 40
- # Fever
if temp > 38.5:
score += 15
if temp > 39.5:
@@ -177,7 +162,6 @@ def calculate_priority_score(patient: Patient) -> float:
@app.get("/")
async def root():
- """Root endpoint"""
return {
"message": "VitalLink Backend API",
"version": "1.0.0",
@@ -188,16 +172,12 @@ async def root():
@app.post("/api/checkin")
async def check_in_patient(data: PatientCheckIn):
- """Register a new patient and assign wristband"""
-
if not available_bands:
raise HTTPException(status_code=503, detail="No wristbands available")
- # Assign IDs
patient_id = f"P{len(patients_db) + 100001}"
band_id = available_bands.pop(0)
- # Create patient record
patient = Patient(
patient_id=patient_id,
band_id=band_id,
@@ -212,7 +192,6 @@ async def check_in_patient(data: PatientCheckIn):
patients_db[patient_id] = patient
- # Notify connected clients
await broadcast_update({"type": "patient_added", "patient": patient.dict()})
return {
@@ -224,24 +203,19 @@ async def check_in_patient(data: PatientCheckIn):
@app.post("/api/vitals")
async def receive_vitals(data: VitalsData):
- """Receive vitals data from base station"""
-
patient_id = data.patient_id
if patient_id not in patients_db:
raise HTTPException(status_code=404, detail="Patient not found")
- # Update patient record
patient = patients_db[patient_id]
patient.current_tier = data.tier
patient.last_vitals = data.dict()
- # Store in history (keep last 1000 readings)
vitals_history[patient_id].append(data)
if len(vitals_history[patient_id]) > 1000:
vitals_history[patient_id] = vitals_history[patient_id][-1000:]
- # Broadcast to connected clients
await broadcast_update(
{"type": "vitals_update", "patient_id": patient_id, "vitals": data.dict()}
)
@@ -251,11 +225,8 @@ async def receive_vitals(data: VitalsData):
@app.get("/api/queue")
async def get_queue():
- """Get prioritized queue of active patients"""
-
active_patients = [p for p in patients_db.values() if p.is_active]
- # Calculate priority and sort
queue = []
for patient in active_patients:
priority_score = calculate_priority_score(patient)
@@ -283,7 +254,6 @@ async def get_queue():
)
)
- # Sort by priority (highest first)
queue.sort(key=lambda x: x.priority_score, reverse=True)
return queue
@@ -291,8 +261,6 @@ async def get_queue():
@app.get("/api/patients/{patient_id}")
async def get_patient_details(patient_id: str):
- """Get detailed information about a specific patient"""
-
if patient_id not in patients_db:
raise HTTPException(status_code=404, detail="Patient not found")
@@ -301,25 +269,21 @@ async def get_patient_details(patient_id: str):
return {
"patient": patient.dict(),
- "vitals_history": [v.dict() for v in history[-50:]], # Last 50 readings
+ "vitals_history": [v.dict() for v in history[-50:]],
"priority_score": calculate_priority_score(patient),
}
@app.post("/api/patients/{patient_id}/discharge")
async def discharge_patient(patient_id: str):
- """Discharge a patient and return wristband to pool"""
-
if patient_id not in patients_db:
raise HTTPException(status_code=404, detail="Patient not found")
patient = patients_db[patient_id]
patient.is_active = False
- # Return band to pool
available_bands.append(patient.band_id)
- # Notify clients
await broadcast_update({"type": "patient_discharged", "patient_id": patient_id})
return {"message": "Patient discharged", "band_returned": patient.band_id}
@@ -327,8 +291,6 @@ async def discharge_patient(patient_id: str):
@app.get("/api/stats")
async def get_statistics():
- """Get overall ER statistics"""
-
active_patients = [p for p in patients_db.values() if p.is_active]
tier_counts = {"EMERGENCY": 0, "ALERT": 0, "NORMAL": 0}
@@ -356,34 +318,46 @@ async def get_statistics():
# ============================================================================
-# WEBSOCKET FOR REAL-TIME UPDATES
+# WRISTBAND ENDPOINTS
+# ============================================================================
+
+
+@app.post("/api/wristband-details")
+async def update_wristband_details(data: dict):
+ """Receive wristband details from wristband system"""
+ global wristband_details_cache
+ wristband_details_cache = data
+ return {"status": "updated"}
+
+
+@app.get("/api/wristband-details")
+async def get_cached_wristband_details():
+ """Get cached wristband details"""
+ return wristband_details_cache
+
+
+# ============================================================================
+# WEBSOCKET
# ============================================================================
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
- """WebSocket connection for real-time updates to frontend"""
-
await websocket.accept()
active_websockets.append(websocket)
- # Send initial data
await websocket.send_json(
{"type": "connected", "message": "Connected to VitalLink server"}
)
try:
while True:
- # Keep connection alive and listen for client messages
data = await websocket.receive_text()
- # Could handle client commands here
except:
active_websockets.remove(websocket)
async def broadcast_update(message: dict):
- """Broadcast update to all connected WebSocket clients"""
-
disconnected = []
for websocket in active_websockets:
try:
@@ -391,7 +365,6 @@ async def broadcast_update(message: dict):
except:
disconnected.append(websocket)
- # Remove disconnected clients
for ws in disconnected:
active_websockets.remove(ws)
diff --git a/vitallink/frontend/dashboard/src/App.jsx b/vitallink/frontend/dashboard/src/App.jsx
index 5d9d4c3..56218c1 100644
--- a/vitallink/frontend/dashboard/src/App.jsx
+++ b/vitallink/frontend/dashboard/src/App.jsx
@@ -1,5 +1,7 @@
import React, { useState, useEffect } from 'react';
-import { Activity, AlertCircle, Clock, Users, Bell, Heart, Thermometer, Wind, CheckCircle, UserX } from 'lucide-react';
+import * as LucideIcons from 'lucide-react';
+
+const { Activity, AlertCircle, Clock, Users, Bell, Heart, Thermometer, Wind, CheckCircle, UserX } = LucideIcons;
const API_BASE = 'http://localhost:8000';
@@ -12,9 +14,11 @@ function App() {
average_wait_minutes: 0
});
const [filter, setFilter] = useState('all');
+ const [activeTab, setActiveTab] = useState('patients');
+ const [wristbands, setWristbands] = useState([]);
+ const [selectedWristband, setSelectedWristband] = useState(null);
useEffect(() => {
- // Fetch data from backend
const fetchData = async () => {
try {
const queueResponse = await fetch(`${API_BASE}/api/queue`);
@@ -23,7 +27,9 @@ function App() {
const statsResponse = await fetch(`${API_BASE}/api/stats`);
const statsData = await statsResponse.json();
- // Set patients from backend
+ const wristbandResponse = await fetch(`${API_BASE}/api/wristband-details`);
+ const wristbandData = await wristbandResponse.json();
+
setPatients(queueData.map(p => ({
patient_id: p.patient_id,
band_id: p.band_id,
@@ -34,97 +40,19 @@ function App() {
last_hr: p.last_hr,
last_spo2: p.last_spo2,
last_temp: p.last_temp,
- symptoms: [] // Backend doesn't send symptoms in queue endpoint
+ symptoms: []
})));
setStats(statsData);
+ setWristbands(wristbandData.wristbands || []);
- console.log(`✓ Fetched ${queueData.length} patients from backend`);
+ console.log(`✓ Fetched ${queueData.length} patients and ${wristbandData.wristbands?.length || 0} wristbands`);
} catch (error) {
console.error('Failed to fetch from backend:', error);
- console.log('⚠️ Using mock data as fallback');
- generateMockPatients();
}
};
- // Mock data fallback function
- const generateMockPatients = () => {
- const mockPatients = [
- {
- patient_id: 'P100001',
- band_id: 'VitalLink-A1B2',
- name: 'John Smith',
- tier: 'NORMAL',
- priority_score: 15.2,
- wait_time_minutes: 22,
- last_hr: 76,
- last_spo2: 98,
- last_temp: 36.8,
- symptoms: ['Chest Pain', 'Nausea']
- },
- {
- patient_id: 'P100002',
- band_id: 'VitalLink-C3D4',
- name: 'Sarah Johnson',
- tier: 'ALERT',
- priority_score: 68.5,
- wait_time_minutes: 45,
- last_hr: 118,
- last_spo2: 93,
- last_temp: 38.4,
- symptoms: ['Fever', 'Difficulty Breathing']
- },
- {
- patient_id: 'P100003',
- band_id: 'VitalLink-E5F6',
- name: 'Michael Chen',
- tier: 'EMERGENCY',
- priority_score: 142.8,
- wait_time_minutes: 8,
- last_hr: 148,
- last_spo2: 86,
- last_temp: 39.7,
- symptoms: ['Severe Headache', 'Chest Pain']
- },
- {
- patient_id: 'P100004',
- band_id: 'VitalLink-G7H8',
- name: 'Emily Davis',
- tier: 'NORMAL',
- priority_score: 18.0,
- wait_time_minutes: 35,
- last_hr: 82,
- last_spo2: 97,
- last_temp: 37.1,
- symptoms: ['Abdominal Pain']
- },
- {
- patient_id: 'P100005',
- band_id: 'VitalLink-I9J0',
- name: 'Robert Williams',
- tier: 'ALERT',
- priority_score: 72.3,
- wait_time_minutes: 52,
- last_hr: 124,
- last_spo2: 91,
- last_temp: 38.8,
- symptoms: ['Fever', 'Dizziness']
- }
- ];
-
- setPatients(mockPatients);
- setStats({
- total_patients: 5,
- active_patients: 5,
- tier_breakdown: { EMERGENCY: 1, ALERT: 2, NORMAL: 2 },
- average_wait_minutes: 32.4
- });
- };
-
- // Initial fetch
fetchData();
-
- // Poll every 3 seconds for updates
const interval = setInterval(fetchData, 3000);
return () => clearInterval(interval);
@@ -173,11 +101,9 @@ function App() {
method: 'POST',
});
console.log(`✓ Discharged patient ${patientId}`);
- // Remove from local state
setPatients(prev => prev.filter(p => p.patient_id !== patientId));
} catch (error) {
console.error('Failed to discharge patient:', error);
- // Still remove from UI even if backend fails
setPatients(prev => prev.filter(p => p.patient_id !== patientId));
}
};
@@ -251,153 +177,351 @@ function App() {
-
-
-
+
+
+
-
-
+
-
- {filteredPatients.map((patient, index) => (
-
-
-
-
-
- #{index + 1}
-
-
-
{patient.name}
-
- {patient.patient_id}
- •
- {patient.band_id}
-
- {patient.symptoms && patient.symptoms.length > 0 && (
-
- {patient.symptoms.map(symptom => (
-
- {symptom}
-
- ))}
+
+ {activeTab === 'patients' ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {filteredPatients.map((patient, index) => (
+
+
+
+
+
+ #{index + 1}
+
+
{patient.name}
+
+ {patient.patient_id}
+ •
+ {patient.band_id}
+
+ {patient.symptoms && patient.symptoms.length > 0 && (
+
+ {patient.symptoms.map(symptom => (
+
+ {symptom}
+
+ ))}
+
+ )}
+
+
+
+
+
+ {getTierIcon(patient.tier)}
+ {patient.tier}
+
+
+
+
+
+
+
+
+
+ Heart Rate
+
+
+ {patient.last_hr}
+
+
bpm
+
+
+
+
+
+ SpO₂
+
+
+ {patient.last_spo2}
+
+
%
+
+
+
+
+
+ Temperature
+
+
+ {patient.last_temp.toFixed(1)}
+
+
°C
+
+
+
+
+
+ Wait Time
+
+
+ {patient.wait_time_minutes}
+
+
minutes
+
+
+
+
+ ))}
+
+
+ {filteredPatients.length === 0 && (
+
+
+
No patients in this category
+
Patients will appear here as they check in
+
+ )}
+ >
+ ) : (
+
+
+
Wristband Inventory
+
+
+ {wristbands.map(band => (
+
setSelectedWristband(band)}
+ className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
+ band.status === 'in_use'
+ ? 'bg-blue-50 border-blue-300 hover:border-blue-400'
+ : 'bg-gray-50 border-gray-300 hover:border-gray-400'
+ }`}
+ >
+
+
+ {band.type === 'real' ? '🔵' : '🟢'} {band.band_id}
+
+
+ {band.status.toUpperCase().replace('_', ' ')}
+
+
+
+ {band.patient_id && (
+
+ Patient: {band.patient_id}
+
+ )}
+
+
+ Packets: {band.packet_count}
+ {band.is_monitoring && (
+ ● LIVE
)}
-
-
-
- {getTierIcon(patient.tier)}
- {patient.tier}
-
-
-
-
-
-
-
-
-
- Heart Rate
-
-
- {patient.last_hr}
-
-
bpm
-
-
-
-
-
- SpO₂
-
-
- {patient.last_spo2}
-
-
%
-
-
-
-
-
- Temperature
-
-
- {patient.last_temp.toFixed(1)}
-
-
°C
-
-
-
-
-
- Wait Time
-
-
- {patient.wait_time_minutes}
-
-
minutes
-
-
+ ))}
-
- ))}
-
- {filteredPatients.length === 0 && (
-
-
-
No patients in this category
-
Patients will appear here as they check in
+ {wristbands.length === 0 && (
+
No wristbands configured
+ )}
+
+
+ {selectedWristband && selectedWristband.last_raw_packet && (
+
+
+
+ Packet Details: {selectedWristband.band_id}
+ {selectedWristband.type === 'simulated' && (
+ MOCK
+ )}
+
+
+
+
+
+
Raw Packet (16 bytes, Hex):
+
+ {selectedWristband.last_raw_packet.hex.toUpperCase().match(/.{1,2}/g).join(' ')}
+
+
+ Format: [ver][seq][timestamp][flags][hr][spo2][temp_x100][activity_x100][checksum][rfu]
+
+
+
+ {selectedWristband.last_raw_packet.decoded && (
+ <>
+
+
Decoded Fields:
+
+
+
Version
+
0x{selectedWristband.last_raw_packet.decoded.version.toString(16).padStart(2, '0')}
+
+
+
Sequence #
+
{selectedWristband.last_raw_packet.decoded.sequence}
+
+
+
Timestamp (ms since boot)
+
{selectedWristband.last_raw_packet.decoded.timestamp_ms.toLocaleString()}
+
+
+
Heart Rate
+
{selectedWristband.last_raw_packet.decoded.hr_bpm}
+
bpm
+
+
+
SpO₂
+
{selectedWristband.last_raw_packet.decoded.spo2}
+
%
+
+
+
Temperature
+
{selectedWristband.last_raw_packet.decoded.temperature_c.toFixed(2)}
+
°C
+
+
+
Activity
+
{selectedWristband.last_raw_packet.decoded.activity.toFixed(2)}
+
RMS
+
+
+
Checksum
+
{selectedWristband.last_raw_packet.decoded.checksum}
+
+
+
Flags (raw)
+
0x{selectedWristband.last_raw_packet.decoded.flags.raw.toString(16).padStart(2, '0')}
+
+
+
+
+ {selectedWristband.last_raw_packet.decoded.flags && (
+
+
Status Flags:
+
+ {selectedWristband.last_raw_packet.decoded.flags.emergency && (
+
+ 🚨 Bit 4: Emergency
+
+ )}
+ {selectedWristband.last_raw_packet.decoded.flags.alert && (
+
+ ⚠️ Bit 3: Alert
+
+ )}
+ {selectedWristband.last_raw_packet.decoded.flags.sensor_fault && (
+
+ ⚙️ Bit 2: Sensor Fault
+
+ )}
+ {selectedWristband.last_raw_packet.decoded.flags.low_battery && (
+
+ 🔋 Bit 1: Low Battery
+
+ )}
+ {selectedWristband.last_raw_packet.decoded.flags.motion_artifact && (
+
+ 👋 Bit 0: Motion Artifact
+
+ )}
+ {!Object.values(selectedWristband.last_raw_packet.decoded.flags).some(v => v === true) && (
+
+ ✓ No flags set (all normal)
+
+ )}
+
+
+ )}
+ >
+ )}
+
+ )}
)}
diff --git a/vitallink/logs/all_pids.txt b/vitallink/logs/all_pids.txt
index 514aaf9..210ed40 100644
--- a/vitallink/logs/all_pids.txt
+++ b/vitallink/logs/all_pids.txt
@@ -1,4 +1,4 @@
-106155
-106168
-106176
-106211
+117391
+117404
+117411
+117454
diff --git a/vitallink/logs/backend.log b/vitallink/logs/backend.log
index c2d4246..5763bbb 100644
Binary files a/vitallink/logs/backend.log and b/vitallink/logs/backend.log differ
diff --git a/vitallink/logs/backend.pid b/vitallink/logs/backend.pid
index daca52c..d4ae7b0 100644
--- a/vitallink/logs/backend.pid
+++ b/vitallink/logs/backend.pid
@@ -1 +1 @@
-106155
+117391
diff --git a/vitallink/logs/dashboard.log b/vitallink/logs/dashboard.log
index 894996b..a8335c6 100644
--- a/vitallink/logs/dashboard.log
+++ b/vitallink/logs/dashboard.log
@@ -5,7 +5,7 @@
Port 5173 is in use, trying another one...
Port 5174 is in use, trying another one...
- VITE v7.1.10 ready in 103 ms
+ VITE v7.1.10 ready in 101 ms
➜ Local: http://localhost:5175/
➜ Network: use --host to expose
diff --git a/vitallink/logs/dashboard.pid b/vitallink/logs/dashboard.pid
index b823426..726a08b 100644
--- a/vitallink/logs/dashboard.pid
+++ b/vitallink/logs/dashboard.pid
@@ -1 +1 @@
-106176
+117411
diff --git a/vitallink/logs/kiosk.log b/vitallink/logs/kiosk.log
index 2085fbe..2b527e7 100644
--- a/vitallink/logs/kiosk.log
+++ b/vitallink/logs/kiosk.log
@@ -6,7 +6,7 @@ Port 5173 is in use, trying another one...
Port 5174 is in use, trying another one...
Port 5175 is in use, trying another one...
- VITE v7.1.10 ready in 103 ms
+ VITE v7.1.10 ready in 102 ms
➜ Local: http://localhost:5176/
➜ Network: use --host to expose
diff --git a/vitallink/logs/kiosk.pid b/vitallink/logs/kiosk.pid
index 72b47a8..4c75e94 100644
--- a/vitallink/logs/kiosk.pid
+++ b/vitallink/logs/kiosk.pid
@@ -1 +1 @@
-106211
+117454
diff --git a/vitallink/logs/wristbands.pid b/vitallink/logs/wristbands.pid
index 06dc9c8..eb4532d 100644
--- a/vitallink/logs/wristbands.pid
+++ b/vitallink/logs/wristbands.pid
@@ -1 +1 @@
-106168
+117404
diff --git a/vitallink/simulator/__pycache__/config_system.cpython-39.pyc b/vitallink/simulator/__pycache__/config_system.cpython-39.pyc
index 5090c61..66fca50 100644
Binary files a/vitallink/simulator/__pycache__/config_system.cpython-39.pyc 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
index 0d0b43a..7835e18 100644
Binary files a/vitallink/simulator/__pycache__/wristband_manager.cpython-39.pyc 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 6597eb4..5c54850 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() or [] # Add "or []"
+ simulated = config.get_simulated_bands() 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() or [] # Add "or []"
+ real = config.get_real_bands() 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 e0486eb..a298c38 100644
--- a/vitallink/simulator/main_runner.py
+++ b/vitallink/simulator/main_runner.py
@@ -1,19 +1,16 @@
"""
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
+import time
+import struct
+from wristband_manager import WristbandManager, WristbandType
from config_system import WristbandConfig
import sys
-# ============================================================================
-# MAIN SYSTEM
-# ============================================================================
-
class VitalLinkSystem:
"""Main system orchestrator"""
@@ -26,38 +23,31 @@ class VitalLinkSystem:
self.monitoring_task = None
async def initialize(self):
- """Initialize the system"""
print("\n" + "=" * 80)
print("VitalLink System Initialization")
print("=" * 80 + "\n")
- # Check backend availability
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):
timeout = self.config.get("scan_timeout", 10.0)
await self.manager.scan_for_real_bands(timeout)
- # Load configured real wristbands
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() or []:
self.manager.add_simulated_band(
band_config["band_id"], band_config.get("profile", "stable")
)
- # Show inventory
self.manager.print_inventory()
async def check_backend(self):
- """Check if backend is running"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(
@@ -66,14 +56,90 @@ class VitalLinkSystem:
if resp.status == 200:
print(f"✓ Backend is running at {self.backend_url}")
return True
- except Exception as e:
+ except:
print(f"❌ Backend not reachable at {self.backend_url}")
return False
return False
+ def _decode_packet_for_display(self, packet: bytes) -> dict:
+ if len(packet) != 16:
+ return {}
+
+ PKT_STRUCT = struct.Struct("
50:
+ self.packet_history = self.packet_history[-50:]
+
+ decoder = PacketDecoder()
+ decoded = decoder.decode(packet)
+
+ if decoded:
+ self.last_packet = decoded
+ self.packet_count += 1
+ await self._send_to_backend(decoded)
+
+ tier = self.simulator.tier
+ interval = 1.0 if tier in ["ALERT", "EMERGENCY"] else 60.0
+ await asyncio.sleep(interval)
+
+ async def _send_to_backend(self, decoded: dict):
+ if not self.patient_id:
+ return
+
+ payload = {
+ "band_id": self.band_id,
+ "patient_id": self.patient_id,
+ "timestamp": time.time(),
+ "ver": decoded["ver"],
+ "seq": decoded["seq"],
+ "ts_ms": decoded["ts_ms"],
+ "tier": decoded["tier"],
+ "flags": [],
+ "hr_bpm": decoded["hr_bpm"],
+ "spo2": decoded["spo2"],
+ "temp_c": decoded["temp_c"],
+ "activity": decoded["activity"],
+ }
+
+ try:
+ async with aiohttp.ClientSession() as session:
+ await session.post(f"{BACKEND_URL}/api/vitals", json=payload)
+ except:
+ pass
+
+ async def stop_monitoring(self):
self.running = False
self.status = WristbandStatus.AVAILABLE
print(f"✓ Stopped simulated wristband {self.band_id}")
@@ -313,17 +300,15 @@ class SimulatedWristband(BaseWristband):
class WristbandManager:
- """Central manager for all wristbands (real and simulated)"""
+ """Central manager for all wristbands"""
def __init__(self):
self.inventory: Dict[str, BaseWristband] = {}
self.active_monitoring: Dict[str, asyncio.Task] = {}
def add_simulated_band(self, band_id: str, profile: str = "stable"):
- """Add a simulated wristband to inventory"""
- # Change naming to use MOCK prefix if not already present
if not band_id.startswith("MOCK-"):
- band_id = f"MOCK-{band_id.replace('VitalLink-', '')}"
+ band_id = f"MOCK-{band_id.replace('VitalLink-', '').replace('MOCK-', '')}"
band = SimulatedWristband(band_id, profile)
self.inventory[band_id] = band
@@ -331,7 +316,6 @@ class WristbandManager:
return band
def add_real_band(self, band_id: str, ble_address: str):
- """Add a real wristband to inventory"""
if not BLE_AVAILABLE:
print("❌ Cannot add real band: Bleak not installed")
return None
@@ -342,7 +326,6 @@ class WristbandManager:
return band
async def scan_for_real_bands(self, timeout: float = 10.0):
- """Scan for real wristbands and add them to inventory"""
if not BLE_AVAILABLE:
print("❌ BLE scanning not available: Install bleak")
return []
@@ -352,7 +335,6 @@ class WristbandManager:
found = []
for device in devices:
- # Check if device advertises our service UUID
uuids = device.metadata.get("uuids", [])
if any(uuid.lower() == SERVICE_UUID.lower() for uuid in uuids):
band_id = (
@@ -365,7 +347,6 @@ class WristbandManager:
return found
def get_available_bands(self) -> List[BaseWristband]:
- """Get list of available (not assigned) wristbands"""
return [
b for b in self.inventory.values() if b.status == WristbandStatus.AVAILABLE
]
@@ -373,20 +354,15 @@ class WristbandManager:
def assign_band(
self, patient_id: str, prefer_real: bool = False
) -> Optional[BaseWristband]:
- """Assign an available band to a patient"""
available = self.get_available_bands()
if not available:
print("❌ No wristbands available")
return None
- # Prefer real bands if requested and available
if prefer_real:
real_bands = [b for b in available if b.type == WristbandType.REAL]
- if real_bands:
- band = real_bands[0]
- else:
- band = available[0]
+ band = real_bands[0] if real_bands else available[0]
else:
band = available[0]
@@ -394,7 +370,6 @@ class WristbandManager:
return band
async def start_monitoring(self, band_id: str):
- """Start monitoring a wristband"""
if band_id not in self.inventory:
print(f"❌ Band {band_id} not in inventory")
return
@@ -404,12 +379,10 @@ class WristbandManager:
print(f"⚠️ {band_id} already being monitored")
return
- # Create monitoring task
task = asyncio.create_task(band.start_monitoring())
self.active_monitoring[band_id] = task
async def stop_monitoring(self, band_id: str):
- """Stop monitoring a wristband"""
if band_id in self.active_monitoring:
task = self.active_monitoring[band_id]
task.cancel()
@@ -419,13 +392,11 @@ class WristbandManager:
await self.inventory[band_id].stop_monitoring()
async def release_band(self, band_id: str):
- """Release band back to inventory"""
await self.stop_monitoring(band_id)
if band_id in self.inventory:
self.inventory[band_id].release()
def get_status(self) -> dict:
- """Get overall status"""
status_counts = {}
for status in WristbandStatus:
status_counts[status.value] = sum(
@@ -445,7 +416,6 @@ class WristbandManager:
}
def print_inventory(self):
- """Print current inventory"""
print("\n" + "=" * 80)
print("WRISTBAND INVENTORY")
print("=" * 80)
@@ -471,57 +441,3 @@ class WristbandManager:
f"Active: {status['active_monitoring']}"
)
print("=" * 80 + "\n")
-
-
-# ============================================================================
-# EXAMPLE USAGE
-# ============================================================================
-
-
-async def main():
- """Example usage of wristband manager"""
-
- manager = WristbandManager()
-
- print("VitalLink Wristband Management System")
- print("=" * 80)
-
- # Option 1: Scan for real wristbands
- # await manager.scan_for_real_bands(timeout=10.0)
-
- # Option 2: Add simulated wristbands
- manager.add_simulated_band("VitalLink-SIM1", "stable")
- manager.add_simulated_band("VitalLink-SIM2", "mild_anxiety")
- manager.add_simulated_band("VitalLink-SIM3", "deteriorating")
-
- # Option 3: Manually add real wristband if you know the address
- # manager.add_real_band("VitalLink-REAL1", "D7:91:3F:9A:12:34")
-
- # Show inventory
- manager.print_inventory()
-
- # Assign bands to patients
- band1 = manager.assign_band("P100001")
- band2 = manager.assign_band("P100002")
-
- # Start monitoring
- if band1:
- await manager.start_monitoring(band1.band_id)
- if band2:
- await manager.start_monitoring(band2.band_id)
-
- # Monitor for 30 seconds
- print("\nMonitoring for 30 seconds...")
- await asyncio.sleep(30)
-
- # Stop and release
- if band1:
- await manager.release_band(band1.band_id)
- if band2:
- await manager.release_band(band2.band_id)
-
- manager.print_inventory()
-
-
-if __name__ == "__main__":
- asyncio.run(main())