Files
ContecMonitor/fiveparaminte-main/main.py
T

190 lines
6.2 KiB
Python
Raw Normal View History

import asyncio
import logging
import multiprocessing
import socket
from contextlib import asynccontextmanager
from functools import partial
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import uvicorn
from config import settings
from database import engine, Base
from contec_server import handle_contec_client
from background_tasks import retry_failed_transmissions
from routers import dashboard
from routers.dashboard import get_ws_manager
# Setup Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(settings.log_file),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Create database tables if they don't exist
Base.metadata.create_all(bind=engine)
def _make_contec_handler(port: int):
"""Create a connection handler closure for a specific Contec port."""
async def handler(reader, writer):
await handle_contec_client(reader, writer, port)
return handler
def _get_local_ips():
"""Get all local IP addresses for this machine."""
ips = []
try:
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
ip = info[4][0]
if ip not in ips and not ip.startswith('127.'):
ips.append(ip)
except Exception:
pass
# Also try connecting to an external address to find the primary IP
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
primary_ip = s.getsockname()[0]
s.close()
if primary_ip not in ips:
ips.insert(0, primary_ip)
except Exception:
pass
return ips if ips else ['Could not detect — check ipconfig']
@asynccontextmanager
async def lifespan(app: FastAPI):
servers = []
# === Start Contec CMS7000PLUS TCP Servers ===
logger.info("Starting Contec CMS7000PLUS listeners...")
for port in settings.contec_ports:
try:
contec_server = await asyncio.start_server(
_make_contec_handler(port),
settings.hl7_host,
port
)
servers.append(contec_server)
logger.info(f" [OK] Contec listener on port {port}")
except PermissionError:
logger.warning(
f" [FAIL] Port {port} - Permission denied. "
f"Run as Administrator for ports below 1024."
)
except OSError as e:
if "already in use" in str(e).lower() or getattr(e, 'errno', 0) == 10048:
logger.warning(f" [FAIL] Port {port} - Already in use")
else:
logger.warning(f" [FAIL] Port {port} - {e}")
if servers:
active_ports = []
for s in servers:
for sock in s.sockets:
active_ports.append(sock.getsockname()[1])
app.state.active_ports = active_ports
logger.info(f" Active listener ports: {active_ports}")
else:
app.state.active_ports = []
logger.error("No servers could be started! Check port availability and permissions.")
# === Print startup banner ===
import os
port = int(os.environ.get("PORT", 8000))
local_ips = _get_local_ips()
model = settings.monitor_model
print("\n" + "=" * 65)
print(f" Contec {model} Patient Monitor - Vital Signs Forwarder")
print("=" * 65)
print(f" DASHBOARD: http://localhost:{port}/api/dashboard")
print(f" API Docs: http://localhost:{port}/docs")
print(f" Contec Ports: {settings.contec_ports}")
print(f" Monitor Model: {model}")
print(f" API Target: {settings.target_api_url}")
print(f" Database: {settings.database_url}")
print(f" ---------------------------------------------------------")
print(f" YOUR PC's IP ADDRESSES (configure on your monitor):")
for ip in local_ips:
print(f" -> {ip}")
print("=" * 65)
print(f" Waiting for {model} connections...")
print(f" On your monitor: System Setup > Network > CMS Settings")
print(f" Set Server IP = one of the IPs above, Port = {settings.contec_ports[0] if settings.contec_ports else 511}")
print("=" * 65 + "\n")
# Startup: Start the background retry task
retry_task = asyncio.create_task(retry_failed_transmissions())
yield
# Shutdown
logger.info("Shutting down servers...")
retry_task.cancel()
for server in servers:
server.close()
for server in servers:
await server.wait_closed()
# Initialize FastAPI app
app = FastAPI(
title="Contec CMS7000PLUS Vital Signs Forwarder",
description="Service to receive vital signs from Contec CMS7000PLUS patient monitors and forward them to a REST API",
version="2.0.0",
lifespan=lifespan
)
# Enable CORS for cross-origin app integrations (e.g. Electron apps, local dev servers)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include dashboard routes
app.include_router(dashboard.router, prefix="/api")
# Serve static folder
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")
# === WebSocket endpoint for live vitals ===
@app.websocket("/ws/vitals")
async def websocket_vitals(websocket: WebSocket):
manager = get_ws_manager()
await manager.connect(websocket)
try:
while True:
# Keep connection alive, listen for any client messages
await websocket.receive_text()
except WebSocketDisconnect:
manager.disconnect(websocket)
except Exception:
manager.disconnect(websocket)
if __name__ == "__main__":
# Required for Windows when building with PyInstaller using multiprocessing (uvicorn workers)
multiprocessing.freeze_support()
logger.info("Initializing Application...")
# Run the FastAPI server.
# Notice we run on port 8000 (or PORT env var) for the REST Dashboard, while HL7 is on settings.hl7_port (e.g. 6060)
import os
port = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port)