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:
Miguel 2025-08-15 20:23:10 +02:00
parent 60db337284
commit 3a830fe100
6 changed files with 237 additions and 29 deletions

View File

@ -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
}

View File

@ -36,7 +36,7 @@
{
"configType": "symbol",
"area": "db",
"streaming": false,
"streaming": true,
"symbol": "AUX Blink_1.6S",
"type": "real"
}

View File

@ -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>

View File

@ -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
View File

@ -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"""

View File

@ -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"
}