基于FIFO的C主程序与Python GUI(matplotlib绘图)IPC对接技术问询
Got it, let's work through this IPC setup between your C telemetry controller and Python GUI. I'll break this down into actionable steps with code examples that fit your exact requirements:
Here's the core flow we'll implement:
- Your C main controller processes frame-based telemetry, then sends frame numbers to Python via a FIFO (named pipe).
- The Python GUI runs in a separate process, listens for incoming frame numbers via the FIFO.
- The GUI lets users load/run custom matplotlib plotting scripts, and passes received frame numbers to these scripts to trigger real-time updates.
1. FIFO Implementation (C ↔ Python)
FIFOs are perfect here because they're lightweight, POSIX-compliant, and work seamlessly between C and Python. We'll use a single FIFO for C → Python frame number transmission (add a second FIFO later if you need Python to send commands back to C).
C Side (Main Controller)
This code creates the FIFO (if it doesn't exist) and writes frame numbers to it as your telemetry system generates frames:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> #define FIFO_PATH "/tmp/telemetry_frame_fifo" int main() { // Create FIFO with read/write permissions for all users if (mkfifo(FIFO_PATH, 0666) == -1) { perror("mkfifo failed (might already exist, that's okay)"); } int fifo_fd = open(FIFO_PATH, O_WRONLY); if (fifo_fd == -1) { perror("Failed to open FIFO for writing"); exit(EXIT_FAILURE); } // Simulate telemetry frame generation (replace with your actual frame logic) int frame_counter = 0; while (1) { frame_counter++; // Format frame number as a string (add more data here if needed) char buffer[64]; snprintf(buffer, sizeof(buffer), "%d\n", frame_counter); // Write to FIFO ssize_t bytes_written = write(fifo_fd, buffer, strlen(buffer)); if (bytes_written == -1) { perror("Failed to write to FIFO"); } sleep(1); // Simulate 1Hz frame rate } close(fifo_fd); unlink(FIFO_PATH); // Clean up FIFO on exit return 0; }
Python Side (GUI Listener)
We'll use a background thread to listen to the FIFO so it doesn't block the GUI's main loop:
import os import threading FIFO_PATH = "/tmp/telemetry_frame_fifo" class FIFOFramelistener(threading.Thread): def __init__(self, frame_update_callback): super().__init__(daemon=True) self.callback = frame_update_callback self.running = True def run(self): # Ensure FIFO exists before opening if not os.path.exists(FIFO_PATH): os.mkfifo(FIFO_PATH) # Open FIFO in read mode (blocks until C process opens it for writing) with open(FIFO_PATH, "r") as fifo: while self.running: line = fifo.readline() if line: try: frame_num = int(line.strip()) self.callback(frame_num) except ValueError: print(f"Invalid frame data received: {line}") def stop(self): self.running = False
2. Python GUI with Script Loading & Plot Updates
We'll use Tkinter (built-in, no extra installs) for the GUI, and matplotlib for plotting. The GUI will let users load custom .py scripts, initialize their plots, and pass frame numbers to trigger updates.
Core GUI Code
import tkinter as tk from tkinter import filedialog import importlib.util import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class TelemetryPlotGUI: def __init__(self, root): self.root = root self.root.title("Telemetry Plot Manager") # State variables self.plot_script = None self.figure = None self.canvas = None # UI Elements self.load_btn = tk.Button(root, text="Load Custom Plot Script", command=self.load_plot_script) self.load_btn.pack(pady=10) # Start FIFO listener self.fifo_listener = FIFOFramelistener(self.on_frame_received) self.fifo_listener.start() # Cleanup on window close self.root.protocol("WM_DELETE_WINDOW", self.shutdown) def load_plot_script(self): # Let user select a .py file script_path = filedialog.askopenfilename(filetypes=[("Python Scripts", "*.py")]) if not script_path: return # Dynamically import the script spec = importlib.util.spec_from_file_location("custom_plot", script_path) self.plot_script = importlib.util.module_from_spec(spec) spec.loader.exec_module(self.plot_script) # Initialize plot from the script if hasattr(self.plot_script, "init_plot"): self.figure = self.plot_script.init_plot() # Embed matplotlib figure into Tkinter self.canvas = FigureCanvasTkAgg(self.figure, master=self.root) self.canvas.draw() self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) else: print("Error: Script must have an `init_plot()` function") def on_frame_received(self, frame_num): # Pass frame number to the plot script (run on GUI main thread to avoid crashes) if self.plot_script and hasattr(self.plot_script, "update_plot"): self.root.after(0, self.plot_script.update_plot, frame_num, self.figure) else: print("No plot script loaded, or missing `update_plot()` function") def shutdown(self): self.fifo_listener.stop() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = TelemetryPlotGUI(root) root.mainloop()
3. Example Custom Plot Script
Users need to write scripts that follow a simple interface:
init_plot(): Returns a matplotlib figure object (sets up axes, labels, initial lines)update_plot(frame_num, figure): Updates the plot using the received frame number
Here's a sample script:
import matplotlib.pyplot as plt import numpy as np # Store historical data for plotting frame_history = [] telemetry_data = [] def init_plot(): fig, ax = plt.subplots(figsize=(8, 4)) ax.set_xlabel("Frame Number") ax.set_ylabel("Simulated Telemetry Value") ax.set_title("Real-Time Telemetry Feed") # Initialize an empty line to update later global plot_line plot_line, = ax.plot([], [], "b-", linewidth=2) return fig def update_plot(frame_num, figure): # Simulate fetching telemetry data for this frame (replace with your logic) # In practice, you could load data from a file/database using the frame_num data_point = np.random.randn() * 5 + 20 # Update history frame_history.append(frame_num) telemetry_data.append(data_point) # Update plot line plot_line.set_data(frame_history, telemetry_data) # Auto-scale axes to fit new data ax = figure.gca() ax.relim() ax.autoscale_view() # Refresh the plot figure.canvas.draw_idle()
Key Notes & Best Practices
- FIFO Blocking: The C process will block on
write()if the Python GUI isn't running and listening. To fix this, open the FIFO in non-blocking mode (O_WRONLY | O_NONBLOCK) in C, and handleEAGAINerrors by retrying later. - Thread Safety: Always update matplotlib plots from the GUI's main thread (we use
root.after(0, ...)to do this) to avoid crashes. - Extendable Data: If you need to send more than just frame numbers, serialize data to JSON (use
cJSONin C,jsonmodule in Python) for easy parsing. - Script Validation: Add checks in the GUI to ensure loaded scripts have the required
init_plot()andupdate_plot()functions before allowing execution.
内容的提问来源于stack exchange,提问作者csandy83

