Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- README.md +1 -1
- frontend/src/context/TelemetryContext.tsx +13 -54
- frontend/src/hooks/useResilientSocket.ts +59 -0
README.md
CHANGED
|
@@ -31,7 +31,7 @@ pinned: false
|
|
| 31 |
|
| 32 |
---
|
| 33 |
|
| 34 |
-
HeartFlow OS
|
| 35 |
|
| 36 |
## System Architecture
|
| 37 |
|
|
|
|
| 31 |
|
| 32 |
---
|
| 33 |
|
| 34 |
+
HeartFlow OS streams cardiovascular vitals, predicts anomalies using a soft-voting ensemble, and exports models to highly-constrained edge devices (ESP32/Arduino).
|
| 35 |
|
| 36 |
## System Architecture
|
| 37 |
|
frontend/src/context/TelemetryContext.tsx
CHANGED
|
@@ -1,14 +1,13 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { createContext, useContext,
|
|
|
|
| 4 |
|
| 5 |
type StreamState = {
|
| 6 |
hr: number[];
|
| 7 |
temp: number[];
|
| 8 |
};
|
| 9 |
|
| 10 |
-
type ConnectionStatus = 'connected' | 'reconnecting' | 'disconnected';
|
| 11 |
-
|
| 12 |
type TelemetryContextType = {
|
| 13 |
data: any;
|
| 14 |
stream: StreamState;
|
|
@@ -23,60 +22,20 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
| 23 |
hr: Array(50).fill(75),
|
| 24 |
temp: Array(50).fill(37.0)
|
| 25 |
});
|
| 26 |
-
const [status, setStatus] = useState<ConnectionStatus>('disconnected');
|
| 27 |
-
|
| 28 |
-
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
| 29 |
-
const reconnectAttemptRef = useRef(0);
|
| 30 |
-
|
| 31 |
-
useEffect(() => {
|
| 32 |
-
let ws: WebSocket;
|
| 33 |
-
let isComponentMounted = true;
|
| 34 |
-
|
| 35 |
-
const connect = () => {
|
| 36 |
-
ws = new WebSocket(process.env.NEXT_PUBLIC_WS_URL || "ws://localhost:8000/ws/feed");
|
| 37 |
-
|
| 38 |
-
ws.onopen = () => {
|
| 39 |
-
if (!isComponentMounted) return;
|
| 40 |
-
setStatus('connected');
|
| 41 |
-
reconnectAttemptRef.current = 0; // Reset attempts
|
| 42 |
-
};
|
| 43 |
-
|
| 44 |
-
ws.onmessage = (event) => {
|
| 45 |
-
if (!isComponentMounted) return;
|
| 46 |
-
const msg = JSON.parse(event.data);
|
| 47 |
-
setData(msg);
|
| 48 |
-
if (msg?.sensor_data) {
|
| 49 |
-
setStream(prev => ({
|
| 50 |
-
hr: [...prev.hr.slice(1), msg.sensor_data.Heart_Rate],
|
| 51 |
-
temp: [...prev.temp.slice(1), msg.sensor_data.Body_Temp]
|
| 52 |
-
}));
|
| 53 |
-
}
|
| 54 |
-
};
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
ws.onerror = () => {
|
| 66 |
-
if (!isComponentMounted) return;
|
| 67 |
-
ws.close(); // Force trigger onclose
|
| 68 |
-
};
|
| 69 |
-
};
|
| 70 |
-
|
| 71 |
-
connect();
|
| 72 |
-
|
| 73 |
-
return () => {
|
| 74 |
-
isComponentMounted = false;
|
| 75 |
-
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
| 76 |
-
if (ws) ws.close();
|
| 77 |
-
};
|
| 78 |
}, []);
|
| 79 |
|
|
|
|
|
|
|
|
|
|
| 80 |
return (
|
| 81 |
<TelemetryContext.Provider value={{ data, stream, status }}>
|
| 82 |
{children}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { createContext, useContext, useState, useCallback } from "react";
|
| 4 |
+
import { useResilientSocket, ConnectionStatus } from "@/hooks/useResilientSocket";
|
| 5 |
|
| 6 |
type StreamState = {
|
| 7 |
hr: number[];
|
| 8 |
temp: number[];
|
| 9 |
};
|
| 10 |
|
|
|
|
|
|
|
| 11 |
type TelemetryContextType = {
|
| 12 |
data: any;
|
| 13 |
stream: StreamState;
|
|
|
|
| 22 |
hr: Array(50).fill(75),
|
| 23 |
temp: Array(50).fill(37.0)
|
| 24 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
const handleMessage = useCallback((msg: any) => {
|
| 27 |
+
setData(msg);
|
| 28 |
+
if (msg?.sensor_data) {
|
| 29 |
+
setStream(prev => ({
|
| 30 |
+
hr: [...prev.hr.slice(1), msg.sensor_data.Heart_Rate],
|
| 31 |
+
temp: [...prev.temp.slice(1), msg.sensor_data.Body_Temp]
|
| 32 |
+
}));
|
| 33 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
}, []);
|
| 35 |
|
| 36 |
+
const wsUrl = process.env.NEXT_PUBLIC_WS_URL || "ws://localhost:8000/ws/feed";
|
| 37 |
+
const status = useResilientSocket(wsUrl, handleMessage);
|
| 38 |
+
|
| 39 |
return (
|
| 40 |
<TelemetryContext.Provider value={{ data, stream, status }}>
|
| 41 |
{children}
|
frontend/src/hooks/useResilientSocket.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useRef, useState } from "react";
|
| 2 |
+
|
| 3 |
+
export type ConnectionStatus = 'connected' | 'reconnecting' | 'disconnected';
|
| 4 |
+
|
| 5 |
+
export function useResilientSocket(url: string, onMessage: (msg: any) => void) {
|
| 6 |
+
const [status, setStatus] = useState<ConnectionStatus>('disconnected');
|
| 7 |
+
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
| 8 |
+
const reconnectAttemptRef = useRef(0);
|
| 9 |
+
const savedCallback = useRef(onMessage);
|
| 10 |
+
|
| 11 |
+
useEffect(() => {
|
| 12 |
+
savedCallback.current = onMessage;
|
| 13 |
+
}, [onMessage]);
|
| 14 |
+
|
| 15 |
+
useEffect(() => {
|
| 16 |
+
let ws: WebSocket;
|
| 17 |
+
let isComponentMounted = true;
|
| 18 |
+
|
| 19 |
+
const connect = () => {
|
| 20 |
+
ws = new WebSocket(url);
|
| 21 |
+
|
| 22 |
+
ws.onopen = () => {
|
| 23 |
+
if (!isComponentMounted) return;
|
| 24 |
+
setStatus('connected');
|
| 25 |
+
reconnectAttemptRef.current = 0; // Reset attempts
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
ws.onmessage = (event) => {
|
| 29 |
+
if (!isComponentMounted) return;
|
| 30 |
+
const msg = JSON.parse(event.data);
|
| 31 |
+
savedCallback.current(msg);
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
ws.onclose = () => {
|
| 35 |
+
if (!isComponentMounted) return;
|
| 36 |
+
setStatus('reconnecting');
|
| 37 |
+
const timeout = Math.min(1000 * (2 ** reconnectAttemptRef.current), 30000); // Max 30s
|
| 38 |
+
reconnectAttemptRef.current += 1;
|
| 39 |
+
|
| 40 |
+
reconnectTimeoutRef.current = setTimeout(connect, timeout);
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
ws.onerror = () => {
|
| 44 |
+
if (!isComponentMounted) return;
|
| 45 |
+
ws.close(); // Force trigger onclose
|
| 46 |
+
};
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
connect();
|
| 50 |
+
|
| 51 |
+
return () => {
|
| 52 |
+
isComponentMounted = false;
|
| 53 |
+
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
| 54 |
+
if (ws) ws.close();
|
| 55 |
+
};
|
| 56 |
+
}, [url]);
|
| 57 |
+
|
| 58 |
+
return status;
|
| 59 |
+
}
|