feat: implement Contec CMS7000PLUS TCP server and dashboard routing with deployment configuration

This commit is contained in:
2026-07-13 12:58:45 +05:30
parent 84b8acc904
commit 6d0f3237af
8 changed files with 108 additions and 5 deletions
+5
View File
@@ -24,6 +24,11 @@ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
# Scratch / debug
scratch/
captures/
*.bin
# IDE / OS # IDE / OS
.idea/ .idea/
.vscode/ .vscode/
+1
View File
@@ -0,0 +1 @@
web: uvicorn main:app --host 0.0.0.0 --port $PORT
+1
View File
@@ -41,3 +41,4 @@ When the application is running, it starts a web server on port `8000`. You can
- `GET /api/devices` - `GET /api/devices`
- `GET /api/latest-readings` - `GET /api/latest-readings`
- `GET /api/transmission-logs` - `GET /api/transmission-logs`
# ContectFiveParaMonitor
-3
View File
@@ -65,9 +65,6 @@ async def handle_contec_client(reader: asyncio.StreamReader, writer: asyncio.Str
break break
buffer += data buffer += data
# Temporary debug capture of raw binary stream
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "ab") as f:
f.write(data)
logger.debug(f"Received {len(data)} bytes from {client_ip}:{client_port} on port {port} (buf={len(buffer)})") logger.debug(f"Received {len(data)} bytes from {client_ip}:{client_port} on port {port} (buf={len(buffer)})")
# --------------------------------------------------------------- # ---------------------------------------------------------------
+15
View File
@@ -0,0 +1,15 @@
services:
- type: web
name: contec-monitor-api
runtime: python
buildCommand: pip install -r requirements.txt
startCommand: uvicorn main:app --host 0.0.0.0 --port $PORT
envVars:
- key: PORT
value: 10000
- key: PYTHON_VERSION
value: 3.12.0
- key: TARGET_API_URL
sync: false
- key: API_TOKEN
sync: false
+1 -2
View File
@@ -4,7 +4,6 @@ sqlalchemy
pydantic pydantic
pydantic-settings pydantic-settings
httpx httpx
pyinstaller gunicorn
rich rich
pyserial
websockets websockets
+84
View File
@@ -185,3 +185,87 @@ def get_network_info():
f"6. The dashboard will show data automatically" f"6. The dashboard will show data automatically"
] ]
} }
# === Push Vitals Endpoint (for local-to-cloud sync) ===
from schemas import NormalizedVitals
from models import Patient
@router.post("/push-vitals")
async def push_vitals(vitals: NormalizedVitals, api_key: str = None, db: Session = Depends(get_db)):
"""
Receive vitals pushed from a local instance and store + broadcast them.
This enables the 'split architecture' where:
- Local instance receives data from physical monitors via TCP
- Local instance POSTs vitals here for cloud storage & dashboard access
Optional: pass ?api_key=YOUR_KEY for basic authentication.
"""
from config import settings
# Optional API key check
if settings.api_token and api_key != settings.api_token:
if settings.api_token: # Only enforce if a token is configured
raise HTTPException(status_code=401, detail="Invalid or missing api_key")
try:
# 1. Device tracking
device = db.query(Device).filter(Device.device_id == vitals.device_id).first()
if not device:
device = Device(
device_id=vitals.device_id,
ip_address=vitals.ip_address or "remote-push",
status="active"
)
db.add(device)
else:
if vitals.ip_address:
device.ip_address = vitals.ip_address
device.status = "active"
device.last_seen = datetime.now(timezone.utc)
# 2. Patient tracking
patient = db.query(Patient).filter(Patient.patient_id == vitals.patient_id).first()
if not patient:
patient = Patient(patient_id=vitals.patient_id, name="Unknown")
db.add(patient)
# 3. Create vital reading
from models import VitalReading as VR
reading = VR(
device_id=vitals.device_id,
patient_id=vitals.patient_id,
timestamp=vitals.timestamp,
heart_rate=vitals.heart_rate,
spo2=vitals.spo2,
systolic_bp=vitals.systolic_bp,
diastolic_bp=vitals.diastolic_bp,
map_bp=vitals.map_bp,
respiratory_rate=vitals.respiratory_rate,
temperature=vitals.temperature,
transmitted=True # Already received = transmitted
)
db.add(reading)
db.commit()
db.refresh(reading)
# 4. Broadcast to WebSocket clients
await ws_manager.broadcast_vitals(vitals.model_dump(mode="json"))
logger.info(f"Push-vitals received for device {vitals.device_id}")
return {
"status": "ok",
"reading_id": reading.id,
"device_id": vitals.device_id,
"patient_id": vitals.patient_id
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Push-vitals error: {e}")
db.rollback()
raise HTTPException(status_code=500, detail=f"Failed to store vitals: {str(e)}")
+1
View File
@@ -0,0 +1 @@
python-3.12.0