added wristband viewing inside of the control software for real and mock wristbands
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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']}")
|
||||
|
||||
@@ -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("<B H I B B B h H B B")
|
||||
(
|
||||
ver,
|
||||
seq,
|
||||
ts_ms,
|
||||
flags,
|
||||
hr_bpm,
|
||||
spo2,
|
||||
skin_c_x100,
|
||||
act_rms_x100,
|
||||
checksum,
|
||||
rfu,
|
||||
) = PKT_STRUCT.unpack(packet)
|
||||
|
||||
return {
|
||||
"version": ver,
|
||||
"sequence": seq,
|
||||
"timestamp_ms": ts_ms,
|
||||
"flags": {
|
||||
"raw": flags,
|
||||
"motion_artifact": bool(flags & (1 << 0)),
|
||||
"low_battery": bool(flags & (1 << 1)),
|
||||
"sensor_fault": bool(flags & (1 << 2)),
|
||||
"alert": bool(flags & (1 << 3)),
|
||||
"emergency": bool(flags & (1 << 4)),
|
||||
},
|
||||
"hr_bpm": hr_bpm,
|
||||
"spo2": spo2,
|
||||
"temperature_c": skin_c_x100 / 100.0,
|
||||
"activity": act_rms_x100 / 100.0,
|
||||
"checksum": f"0x{checksum:02X}",
|
||||
"reserved": rfu,
|
||||
}
|
||||
|
||||
async def report_inventory_to_backend(self):
|
||||
try:
|
||||
inventory = {"timestamp": time.time(), "wristbands": []}
|
||||
|
||||
for band_id, band in self.manager.inventory.items():
|
||||
band_info = {
|
||||
"band_id": band.band_id,
|
||||
"type": band.type.value,
|
||||
"status": band.status.value,
|
||||
"patient_id": band.patient_id,
|
||||
"packet_count": band.packet_count,
|
||||
"is_monitoring": band_id in self.manager.active_monitoring,
|
||||
"last_packet": band.last_packet,
|
||||
}
|
||||
|
||||
if band.type == WristbandType.SIMULATED and hasattr(
|
||||
band, "last_raw_packet"
|
||||
):
|
||||
if band.last_raw_packet:
|
||||
band_info["last_raw_packet"] = {
|
||||
"hex": band.last_raw_packet.hex(),
|
||||
"bytes": list(band.last_raw_packet),
|
||||
"decoded": self._decode_packet_for_display(
|
||||
band.last_raw_packet
|
||||
),
|
||||
}
|
||||
|
||||
if hasattr(band, "packet_history"):
|
||||
band_info["recent_packets"] = band.packet_history[-5:]
|
||||
|
||||
inventory["wristbands"].append(band_info)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
await session.post(
|
||||
f"{self.backend_url}/api/wristband-details", json=inventory
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
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()
|
||||
@@ -89,11 +155,9 @@ class VitalLinkSystem:
|
||||
for patient in queue:
|
||||
patient_id = patient["patient_id"]
|
||||
|
||||
# New patient detected
|
||||
if patient_id not in known_patients:
|
||||
known_patients.add(patient_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
|
||||
@@ -105,7 +169,6 @@ class VitalLinkSystem:
|
||||
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
|
||||
)
|
||||
@@ -114,18 +177,15 @@ class VitalLinkSystem:
|
||||
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}"
|
||||
emergency_band_id = f"MOCK-EMRG{len(self.manager.inventory):02d}"
|
||||
band = self.manager.add_simulated_band(
|
||||
emergency_band_id, "stable"
|
||||
)
|
||||
@@ -134,20 +194,15 @@ class VitalLinkSystem:
|
||||
print(
|
||||
f" ✓ Created and assigned {emergency_band_id}"
|
||||
)
|
||||
|
||||
await self.manager.start_monitoring(
|
||||
band.band_id
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Silently continue if backend temporarily unavailable
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check every 2 seconds
|
||||
await asyncio.sleep(2)
|
||||
|
||||
async def run(self):
|
||||
"""Run the main system"""
|
||||
self.running = True
|
||||
|
||||
await self.initialize()
|
||||
@@ -164,15 +219,12 @@ class VitalLinkSystem:
|
||||
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
|
||||
while self.running:
|
||||
await asyncio.sleep(10)
|
||||
|
||||
# Periodic status update
|
||||
status = self.manager.get_status()
|
||||
available = status["status_breakdown"].get("available", 0)
|
||||
|
||||
@@ -183,19 +235,18 @@ class VitalLinkSystem:
|
||||
f"Sim: {status['simulated_bands']}"
|
||||
)
|
||||
|
||||
await self.report_inventory_to_backend()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
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)
|
||||
@@ -204,82 +255,10 @@ class VitalLinkSystem:
|
||||
self.manager.print_inventory()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI MODES
|
||||
# ============================================================================
|
||||
|
||||
|
||||
async def interactive_mode():
|
||||
"""Interactive mode with menu"""
|
||||
system = VitalLinkSystem()
|
||||
await system.initialize()
|
||||
|
||||
while True:
|
||||
print("\n" + "=" * 80)
|
||||
print("VITALLINK INTERACTIVE MODE")
|
||||
print("=" * 80)
|
||||
print("\n1. Show inventory")
|
||||
print("2. Scan for real wristbands")
|
||||
print("3. Assign band to patient")
|
||||
print("4. Release band")
|
||||
print("5. Start auto-monitoring mode")
|
||||
print("6. Exit")
|
||||
|
||||
choice = input("\nSelect option: ")
|
||||
|
||||
if choice == "1":
|
||||
system.manager.print_inventory()
|
||||
|
||||
elif choice == "2":
|
||||
await system.manager.scan_for_real_bands(timeout=10.0)
|
||||
|
||||
elif choice == "3":
|
||||
patient_id = input("Patient ID: ")
|
||||
prefer_real = input("Prefer real band? (y/n): ").lower() == "y"
|
||||
band = system.manager.assign_band(patient_id, prefer_real=prefer_real)
|
||||
if band:
|
||||
await system.manager.start_monitoring(band.band_id)
|
||||
|
||||
elif choice == "4":
|
||||
band_id = input("Band ID to release: ")
|
||||
await system.manager.release_band(band_id)
|
||||
|
||||
elif choice == "5":
|
||||
await system.run()
|
||||
break
|
||||
|
||||
elif choice == "6":
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MAIN ENTRY POINT
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="VitalLink System")
|
||||
parser.add_argument(
|
||||
"--interactive", "-i", action="store_true", help="Run in interactive mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
"-c",
|
||||
default="wristband_config.yaml",
|
||||
help="Configuration file path",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.interactive:
|
||||
asyncio.run(interactive_mode())
|
||||
else:
|
||||
# Normal automatic mode
|
||||
system = VitalLinkSystem()
|
||||
asyncio.run(system.run())
|
||||
system = VitalLinkSystem()
|
||||
asyncio.run(system.run())
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nExiting...")
|
||||
sys.exit(0)
|
||||
|
||||
@@ -5,6 +5,7 @@ Unified system for managing real and simulated wristbands
|
||||
|
||||
import asyncio
|
||||
import struct
|
||||
import random
|
||||
import time
|
||||
import aiohttp
|
||||
from typing import Dict, List, Optional, Union
|
||||
@@ -40,15 +41,15 @@ class WristbandType(Enum):
|
||||
|
||||
|
||||
class WristbandStatus(Enum):
|
||||
AVAILABLE = "available" # Charged and ready
|
||||
ASSIGNED = "assigned" # Checked out to patient
|
||||
IN_USE = "in_use" # Actively sending data
|
||||
CHARGING = "charging" # On charger
|
||||
MAINTENANCE = "maintenance" # Needs attention
|
||||
AVAILABLE = "available"
|
||||
ASSIGNED = "assigned"
|
||||
IN_USE = "in_use"
|
||||
CHARGING = "charging"
|
||||
MAINTENANCE = "maintenance"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PACKET DECODER (Works with real hardware packets)
|
||||
# PACKET DECODER
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -61,13 +62,11 @@ class PacketDecoder:
|
||||
if len(data) != 16:
|
||||
return None
|
||||
|
||||
# Verify checksum
|
||||
checksum_calc = sum(data[0:14]) & 0xFF
|
||||
if checksum_calc != data[14]:
|
||||
print(f"⚠️ Checksum failed: expected {data[14]}, got {checksum_calc}")
|
||||
return None
|
||||
|
||||
# Unpack according to spec
|
||||
(
|
||||
ver,
|
||||
seq,
|
||||
@@ -81,11 +80,10 @@ class PacketDecoder:
|
||||
rfu,
|
||||
) = PKT_STRUCT.unpack(data)
|
||||
|
||||
# Determine tier from flags
|
||||
tier = "NORMAL"
|
||||
if flags & (1 << 4): # EMERGENCY bit
|
||||
if flags & (1 << 4):
|
||||
tier = "EMERGENCY"
|
||||
elif flags & (1 << 3): # ALERT bit
|
||||
elif flags & (1 << 3):
|
||||
tier = "ALERT"
|
||||
|
||||
return {
|
||||
@@ -108,7 +106,7 @@ class PacketDecoder:
|
||||
|
||||
|
||||
class BaseWristband(ABC):
|
||||
"""Abstract base class for all wristbands (real and simulated)"""
|
||||
"""Abstract base class for all wristbands"""
|
||||
|
||||
def __init__(self, band_id: str, wristband_type: WristbandType):
|
||||
self.band_id = band_id
|
||||
@@ -120,22 +118,18 @@ class BaseWristband(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def start_monitoring(self):
|
||||
"""Start receiving data from wristband"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def stop_monitoring(self):
|
||||
"""Stop receiving data"""
|
||||
pass
|
||||
|
||||
def assign_to_patient(self, patient_id: str):
|
||||
"""Assign wristband to a patient"""
|
||||
self.patient_id = patient_id
|
||||
self.status = WristbandStatus.ASSIGNED
|
||||
print(f"✓ {self.band_id} assigned to patient {patient_id}")
|
||||
|
||||
def release(self):
|
||||
"""Release wristband (return to inventory)"""
|
||||
self.patient_id = None
|
||||
self.status = WristbandStatus.AVAILABLE
|
||||
self.packet_count = 0
|
||||
@@ -143,7 +137,7 @@ class BaseWristband(ABC):
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# REAL WRISTBAND (Hardware BLE)
|
||||
# REAL WRISTBAND
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -157,7 +151,6 @@ class RealWristband(BaseWristband):
|
||||
self.decoder = PacketDecoder()
|
||||
|
||||
async def start_monitoring(self):
|
||||
"""Connect to real wristband via BLE and start receiving data"""
|
||||
if not BLE_AVAILABLE:
|
||||
raise RuntimeError("Bleak library not available")
|
||||
|
||||
@@ -171,7 +164,6 @@ class RealWristband(BaseWristband):
|
||||
await self.client.connect()
|
||||
print(f"✓ Connected to {self.band_id}")
|
||||
|
||||
# Subscribe to notifications
|
||||
await self.client.start_notify(CHAR_UUID, self._notification_handler)
|
||||
print(f"✓ Subscribed to notifications from {self.band_id}")
|
||||
|
||||
@@ -180,102 +172,13 @@ class RealWristband(BaseWristband):
|
||||
self.status = WristbandStatus.MAINTENANCE
|
||||
|
||||
def _notification_handler(self, sender, data: bytearray):
|
||||
"""Handle incoming BLE notifications"""
|
||||
decoded = self.decoder.decode(bytes(data))
|
||||
if decoded:
|
||||
self.last_packet = decoded
|
||||
self.packet_count += 1
|
||||
|
||||
# Send to backend
|
||||
asyncio.create_task(self._send_to_backend(decoded))
|
||||
|
||||
async def _send_to_backend(self, decoded: dict):
|
||||
"""Send decoded data to backend"""
|
||||
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": [], # Convert flags to list if needed
|
||||
"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 # Silently fail if backend unavailable
|
||||
|
||||
async def stop_monitoring(self):
|
||||
"""Disconnect from wristband"""
|
||||
if self.client and self.client.is_connected:
|
||||
await self.client.stop_notify(CHAR_UUID)
|
||||
await self.client.disconnect()
|
||||
print(f"✓ Disconnected from {self.band_id}")
|
||||
|
||||
self.status = WristbandStatus.AVAILABLE
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SIMULATED WRISTBAND (For testing without hardware)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SimulatedWristband(BaseWristband):
|
||||
"""Simulated wristband for testing"""
|
||||
|
||||
def __init__(self, band_id: str, profile: str = "stable"):
|
||||
super().__init__(band_id, WristbandType.SIMULATED)
|
||||
self.profile = profile
|
||||
self.seq = 0
|
||||
self.running = False
|
||||
|
||||
# Import simulator profiles
|
||||
from wristband_simulator import PATIENT_PROFILES, WristbandSimulator
|
||||
|
||||
self.simulator = WristbandSimulator(
|
||||
band_id, PATIENT_PROFILES.get(profile, PATIENT_PROFILES["stable"]), None
|
||||
)
|
||||
|
||||
async def start_monitoring(self):
|
||||
"""Start simulated data generation"""
|
||||
self.status = WristbandStatus.IN_USE
|
||||
self.running = True
|
||||
self.simulator.patient_id = self.patient_id
|
||||
|
||||
print(f"🟢 Starting simulated wristband {self.band_id} ({self.profile})")
|
||||
|
||||
while self.running:
|
||||
# Generate packet
|
||||
packet = self.simulator.generate_packet()
|
||||
|
||||
# Decode it
|
||||
decoder = PacketDecoder()
|
||||
decoded = decoder.decode(packet)
|
||||
|
||||
if decoded:
|
||||
self.last_packet = decoded
|
||||
self.packet_count += 1
|
||||
|
||||
# Send to backend
|
||||
await self._send_to_backend(decoded)
|
||||
|
||||
# Wait based on tier
|
||||
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):
|
||||
"""Send to backend"""
|
||||
if not self.patient_id:
|
||||
return
|
||||
|
||||
@@ -301,7 +204,91 @@ class SimulatedWristband(BaseWristband):
|
||||
pass
|
||||
|
||||
async def stop_monitoring(self):
|
||||
"""Stop simulation"""
|
||||
if self.client and self.client.is_connected:
|
||||
await self.client.stop_notify(CHAR_UUID)
|
||||
await self.client.disconnect()
|
||||
print(f"✓ Disconnected from {self.band_id}")
|
||||
|
||||
self.status = WristbandStatus.AVAILABLE
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SIMULATED WRISTBAND
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SimulatedWristband(BaseWristband):
|
||||
"""Simulated wristband for testing"""
|
||||
|
||||
def __init__(self, band_id: str, profile: str = "stable"):
|
||||
super().__init__(band_id, WristbandType.SIMULATED)
|
||||
self.profile = profile
|
||||
self.seq = 0
|
||||
self.running = False
|
||||
self.last_raw_packet = None
|
||||
self.packet_history = []
|
||||
|
||||
from wristband_simulator import PATIENT_PROFILES, WristbandSimulator
|
||||
|
||||
self.simulator = WristbandSimulator(
|
||||
band_id, PATIENT_PROFILES.get(profile, PATIENT_PROFILES["stable"]), None
|
||||
)
|
||||
|
||||
async def start_monitoring(self):
|
||||
self.status = WristbandStatus.IN_USE
|
||||
self.running = True
|
||||
self.simulator.patient_id = self.patient_id
|
||||
|
||||
print(f"🟢 Starting simulated wristband {self.band_id} ({self.profile})")
|
||||
|
||||
while self.running:
|
||||
packet = self.simulator.generate_packet()
|
||||
|
||||
self.last_raw_packet = packet
|
||||
self.packet_history.append(
|
||||
{"timestamp": time.time(), "raw": packet.hex(), "bytes": list(packet)}
|
||||
)
|
||||
if len(self.packet_history) > 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())
|
||||
|
||||
Reference in New Issue
Block a user