feat: Add PlotJuggler integration with UDP streaming support, update system state and dataset variables for streaming, and enhance dashboard functionality
This commit is contained in:
parent
60db337284
commit
3a830fe100
|
@ -7602,8 +7602,118 @@
|
|||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:16:35.894381",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_started",
|
||||
"message": "UDP streaming to PlotJuggler started",
|
||||
"details": {
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870,
|
||||
"datasets_available": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:16:56.537497",
|
||||
"level": "info",
|
||||
"event_type": "config_reload",
|
||||
"message": "Dataset configuration reloaded from files with CSV header validation",
|
||||
"details": {
|
||||
"datasets_count": 3,
|
||||
"active_datasets_count": 3,
|
||||
"csv_recording_active": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:16:59.997119",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_stopped",
|
||||
"message": "UDP streaming to PlotJuggler stopped (CSV recording continues)",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:17:05.782835",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_started",
|
||||
"message": "UDP streaming to PlotJuggler started",
|
||||
"details": {
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870,
|
||||
"datasets_available": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:21:47.811533",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:21:47.876040",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:21:47.885336",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:21:47.894836",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:21:47.904356",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_started",
|
||||
"message": "UDP streaming to PlotJuggler started",
|
||||
"details": {
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870,
|
||||
"datasets_available": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:22:34.633059",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_stopped",
|
||||
"message": "UDP streaming to PlotJuggler stopped (CSV recording continues)",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T20:22:39.890817",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_started",
|
||||
"message": "UDP streaming to PlotJuggler started",
|
||||
"details": {
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870,
|
||||
"datasets_available": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-15T20:06:52.289232",
|
||||
"total_entries": 620
|
||||
"last_updated": "2025-08-15T20:22:39.890817",
|
||||
"total_entries": 631
|
||||
}
|
|
@ -36,7 +36,7 @@
|
|||
{
|
||||
"configType": "symbol",
|
||||
"area": "db",
|
||||
"streaming": false,
|
||||
"streaming": true,
|
||||
"symbol": "AUX Blink_1.6S",
|
||||
"type": "real"
|
||||
}
|
||||
|
|
|
@ -315,12 +315,26 @@ function StatusBar({ status, isConnected, isLeader }) {
|
|||
const streaming = !!status?.streaming
|
||||
const csvRecording = !!status?.csv_recording
|
||||
const [actionLoading, setActionLoading] = useState({})
|
||||
const [plotJugglerFound, setPlotJugglerFound] = useState(false)
|
||||
const toast = useToast()
|
||||
|
||||
const setLoading = (action, loading) => {
|
||||
setActionLoading(prev => ({ ...prev, [action]: loading }))
|
||||
}
|
||||
|
||||
// Check if PlotJuggler is available on component mount
|
||||
useEffect(() => {
|
||||
const checkPlotJuggler = async () => {
|
||||
try {
|
||||
const result = await api.getPlotJugglerPath()
|
||||
setPlotJugglerFound(result.found)
|
||||
} catch (error) {
|
||||
setPlotJugglerFound(false)
|
||||
}
|
||||
}
|
||||
checkPlotJuggler()
|
||||
}, [])
|
||||
|
||||
const handleConnectPlc = async () => {
|
||||
setLoading('connect', true)
|
||||
try {
|
||||
|
@ -413,6 +427,28 @@ function StatusBar({ status, isConnected, isLeader }) {
|
|||
}
|
||||
}
|
||||
|
||||
const handleLaunchPlotJuggler = async () => {
|
||||
setLoading('launchPlotJuggler', true)
|
||||
try {
|
||||
const result = await api.launchPlotJugglerStreamer()
|
||||
toast({
|
||||
title: '🚀 PlotJuggler launched',
|
||||
description: result.message || 'PlotJuggler started with UDP streamer',
|
||||
status: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: '❌ Failed to launch PlotJuggler',
|
||||
description: error.message,
|
||||
status: 'error',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
setLoading('launchPlotJuggler', false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={4} mb={6}>
|
||||
<Card>
|
||||
|
@ -464,29 +500,49 @@ function StatusBar({ status, isConnected, isLeader }) {
|
|||
{streaming ? 'Active' : 'Inactive'}
|
||||
</StatNumber>
|
||||
<Box mt={2}>
|
||||
{streaming ? (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleStopStreaming}
|
||||
isLoading={actionLoading.stopStream}
|
||||
loadingText="Stopping..."
|
||||
>
|
||||
⏹️ Stop
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleStartStreaming}
|
||||
isLoading={actionLoading.startStream}
|
||||
loadingText="Starting..."
|
||||
>
|
||||
▶️ Start
|
||||
</Button>
|
||||
)}
|
||||
<VStack spacing={2} align="stretch">
|
||||
{streaming ? (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleStopStreaming}
|
||||
isLoading={actionLoading.stopStream}
|
||||
loadingText="Stopping..."
|
||||
>
|
||||
⏹️ Stop
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleStartStreaming}
|
||||
isLoading={actionLoading.startStream}
|
||||
loadingText="Starting..."
|
||||
>
|
||||
▶️ Start
|
||||
</Button>
|
||||
)}
|
||||
{plotJugglerFound && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="green"
|
||||
variant="outline"
|
||||
onClick={handleLaunchPlotJuggler}
|
||||
isLoading={actionLoading.launchPlotJuggler}
|
||||
loadingText="Launching..."
|
||||
title="Launch PlotJuggler with UDP streamer configured"
|
||||
>
|
||||
🚀 PlotJuggler
|
||||
</Button>
|
||||
)}
|
||||
{!plotJugglerFound && (
|
||||
<Text fontSize="xs" color="gray.500" textAlign="center">
|
||||
PlotJuggler not found
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
|
|
|
@ -263,6 +263,17 @@ export async function setPlotJugglerPath(path) {
|
|||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function launchPlotJugglerStreamer() {
|
||||
const res = await fetch(`${BASE_URL}/api/plotjuggler/launch-streamer`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Open CSV in Excel
|
||||
export async function openCsvInExcel(filePath) {
|
||||
const res = await fetch(`${BASE_URL}/api/csv/open-excel`, {
|
||||
|
|
32
main.py
32
main.py
|
@ -3015,6 +3015,38 @@ def launch_plotjuggler():
|
|||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/plotjuggler/launch-streamer", methods=["POST"])
|
||||
def launch_plotjuggler_streamer():
|
||||
"""Launch PlotJuggler with UDP streamer configured"""
|
||||
try:
|
||||
# Get PlotJuggler path from system state
|
||||
plotjuggler_path = get_plotjuggler_path()
|
||||
if not plotjuggler_path:
|
||||
return jsonify({"error": "PlotJuggler not found"}), 404
|
||||
|
||||
# Launch PlotJuggler with UDP streamer parameters
|
||||
import subprocess
|
||||
|
||||
cmd = [
|
||||
plotjuggler_path,
|
||||
"-n", # no recent files menu
|
||||
"--start_streamer", "UDP streamer"
|
||||
]
|
||||
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": "PlotJuggler launched with UDP streamer",
|
||||
"command": " ".join(cmd),
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/plotjuggler/path", methods=["GET"])
|
||||
def get_plotjuggler_path_endpoint():
|
||||
"""Get PlotJuggler executable path"""
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"last_state": {
|
||||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"should_stream": true,
|
||||
"active_datasets": [
|
||||
"Fast",
|
||||
"Test",
|
||||
|
@ -9,6 +9,5 @@
|
|||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-15T20:15:35.459362",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
"last_update": "2025-08-15T20:22:39.890817"
|
||||
}
|
Loading…
Reference in New Issue