add info to session pannel during monitoring

This commit is contained in:
VALLONGOL 2025-06-17 08:52:39 +02:00
parent 9851a5bd0b
commit d60122e201
4 changed files with 195 additions and 41 deletions

View File

@ -296,6 +296,16 @@ class AppController:
self.is_live_monitoring_active = True
self.live_adapter_thread.start()
self.live_data_processor.start_processing_queue()
if (
self.main_window
and hasattr(self.main_window.function_notebook_panel, "data_logging_panel")
and self.main_window.function_notebook_panel.data_logging_panel
):
self.main_window.function_notebook_panel.data_logging_panel.session_started(
start_timestamp=time.time(),
query_rate_sec=app_config.LIVE_POLLING_INTERVAL_SECONDS
)
if self.aircraft_db_manager and self._active_bounding_box:
session_params = {
@ -321,6 +331,14 @@ class AppController:
module_logger.info(
f"Controller: Stopping live monitoring (from_error={from_error})."
)
if (
self.main_window
and hasattr(self.main_window.function_notebook_panel, "data_logging_panel")
and self.main_window.function_notebook_panel.data_logging_panel
):
self.main_window.function_notebook_panel.data_logging_panel.session_stopped()
self.is_live_monitoring_active = False
if (
@ -380,6 +398,18 @@ class AppController:
if panel:
panel.add_summary_entry(timestamp, aircraft_count)
panel.update_last_query_result(timestamp, aircraft_count)
# This is now the central point for incrementing the query count for any active session
self.increment_session_query_count()
def increment_session_query_count(self):
"""Notifies the DataLoggingPanel to increment its session query counter."""
if (
self.main_window
and hasattr(self.main_window.function_notebook_panel, "data_logging_panel")
and self.main_window.function_notebook_panel.data_logging_panel
):
self.main_window.function_notebook_panel.data_logging_panel.increment_query_count()
def open_log_directory(self, directory_path: str):
if not directory_path:
@ -495,6 +525,17 @@ class AppController:
)
self.historical_adapter_thread.start()
self.historical_data_processor.start_processing()
if (
self.main_window
and hasattr(self.main_window.function_notebook_panel, "data_logging_panel")
and self.main_window.function_notebook_panel.data_logging_panel
):
self.main_window.function_notebook_panel.data_logging_panel.session_started(
start_timestamp=params["start_time"],
query_rate_sec=params["sampling_interval_sec"]
)
self.main_window.update_semaphore_and_status(
GUI_STATUS_FETCHING, "Historical download started."
)
@ -523,6 +564,13 @@ class AppController:
self.aircraft_db_manager.add_scan_history(
self._current_scan_params, self._active_bounding_box, status
)
if (
self.main_window
and hasattr(self.main_window.function_notebook_panel, "data_logging_panel")
and self.main_window.function_notebook_panel.data_logging_panel
):
self.main_window.function_notebook_panel.data_logging_panel.session_stopped()
self.is_historical_download_active = False
self._current_scan_params = None

View File

@ -6,7 +6,8 @@ This panel is designed to be shared across different monitoring modes.
import tkinter as tk
from tkinter import ttk, filedialog, font as tkFont
from typing import Optional, Any
from datetime import datetime, timezone
from datetime import datetime, timezone, timedelta
import time
from flightmonitor.utils.logger import get_logger
@ -29,17 +30,22 @@ class DataLoggingPanel:
self.parent_frame = parent_frame
self.controller = controller
# --- Internal State ---
self.session_start_time: Optional[float] = None
self.session_query_count: int = 0
self.session_query_rate_sec: Optional[int] = None
self._session_update_after_id: Optional[str] = None
# --- Tkinter Variables ---
self.enable_logging_var = tk.BooleanVar(value=False)
self.log_directory_var = tk.StringVar(value="")
self.last_query_result_var = tk.StringVar(value="Last query result: N/A")
self.session_info_var = tk.StringVar(value="Session not active.")
# --- Widget References ---
self.log_dir_entry: Optional[ttk.Entry] = None
self.browse_button: Optional[ttk.Button] = None
self.open_folder_button: Optional[ttk.Button] = (
None # MODIFICATO: Aggiunto riferimento
)
self.open_folder_button: Optional[ttk.Button] = None
self.summary_table: Optional[ttk.Treeview] = None
self._build_ui()
@ -72,7 +78,6 @@ class DataLoggingPanel:
)
self.log_dir_entry.grid(row=0, column=1, sticky=tk.EW)
# MODIFICATO: Aggiunto il nuovo pulsante "Open Folder"
self.browse_button = ttk.Button(
dir_frame,
text="Browse...",
@ -88,16 +93,23 @@ class DataLoggingPanel:
state=tk.DISABLED,
)
self.open_folder_button.grid(row=0, column=3, sticky=tk.E, padx=(2, 0))
# --- FINE MODIFICA ---
last_result_font = tkFont.Font(family="Helvetica", size=9, slant="italic")
info_font = tkFont.Font(family="Helvetica", size=9, slant="italic")
last_result_label = ttk.Label(
container,
textvariable=self.last_query_result_var,
font=last_result_font,
font=info_font,
foreground="navy",
)
last_result_label.pack(anchor=tk.W, pady=(8, 2))
last_result_label.pack(anchor=tk.W, pady=(8, 0))
session_info_label = ttk.Label(
container,
textvariable=self.session_info_var,
font=info_font,
foreground="darkgreen",
)
session_info_label.pack(anchor=tk.W, pady=(0, 2))
table_frame = ttk.Frame(container)
table_frame.pack(fill=tk.BOTH, expand=True, pady=(2, 0))
@ -125,12 +137,8 @@ class DataLoggingPanel:
self.log_dir_entry.config(state=new_state)
if self.browse_button:
self.browse_button.config(state=new_state)
# MODIFICATO: Abilita/disabilita anche il nuovo pulsante
if self.open_folder_button:
self.open_folder_button.config(state=new_state)
module_logger.info(
f"Raw data logging has been {'enabled' if is_enabled else 'disabled'} by user."
)
def _on_browse_directory(self):
directory = filedialog.askdirectory(
@ -138,18 +146,62 @@ class DataLoggingPanel:
)
if directory:
self.log_directory_var.set(directory)
module_logger.info(f"User selected new log directory: {directory}")
# MODIFICATO: Aggiunto il metodo callback per "Open Folder"
def _on_open_folder(self):
"""Callback for the 'Open Folder' button."""
directory_path = self.get_log_directory()
if self.controller and hasattr(self.controller, "open_log_directory"):
self.controller.open_log_directory(directory_path)
else:
module_logger.error(
"Controller or 'open_log_directory' method not available."
)
def session_started(self, start_timestamp: float, query_rate_sec: Optional[int]):
"""To be called by the controller when a monitoring/download session starts."""
self.session_start_time = start_timestamp
self.session_query_count = 0
self.session_query_rate_sec = query_rate_sec
if self._session_update_after_id:
self.parent_frame.after_cancel(self._session_update_after_id)
self._update_session_duration()
module_logger.info(f"DataLoggingPanel session timer started at {start_timestamp}.")
def session_stopped(self):
"""To be called by the controller when a session stops."""
if self._session_update_after_id:
self.parent_frame.after_cancel(self._session_update_after_id)
self._session_update_after_id = None
self.session_start_time = None
self.session_query_count = 0
self.session_query_rate_sec = None
self.session_info_var.set("Session not active.")
module_logger.info("DataLoggingPanel session timer stopped.")
def _update_session_duration(self):
"""Periodically updates the session duration label."""
if self.session_start_time is None:
return
elapsed_seconds = time.time() - self.session_start_time
duration_str = str(timedelta(seconds=int(elapsed_seconds)))
start_time_str = datetime.fromtimestamp(self.session_start_time, timezone.utc).strftime('%H:%M:%S')
rate_str = ""
if self.session_query_rate_sec is not None and self.session_query_rate_sec > 0:
rate_str = f" (1 per {self.session_query_rate_sec:.0f}s)"
session_text = (
f"Session started: {start_time_str} (UTC) | "
f"Duration: {duration_str} | "
f"Queries: {self.session_query_count}{rate_str}"
)
self.session_info_var.set(session_text)
self._session_update_after_id = self.parent_frame.after(1000, self._update_session_duration)
def increment_query_count(self):
"""Increments the session query counter."""
self.session_query_count += 1
# The label will be updated by the next scheduled _update_session_duration call
def is_logging_enabled(self) -> bool:
return self.enable_logging_var.get()
@ -160,17 +212,12 @@ class DataLoggingPanel:
def add_summary_entry(self, timestamp: float, count: int):
if not self.summary_table or not self.summary_table.winfo_exists():
return
try:
dt_object = datetime.fromtimestamp(timestamp, timezone.utc)
time_str = dt_object.strftime("%Y-%m-%d %H:%M:%S")
self.summary_table.insert("", 0, values=(time_str, count))
except (ValueError, TypeError) as e:
module_logger.error(f"Failed to add summary entry to table: {e}")
except tk.TclError:
module_logger.warning(
"TclError adding summary to table, widget likely destroyed."
)
except (ValueError, TypeError, tk.TclError):
pass
def update_last_query_result(self, timestamp: float, count: int):
try:
@ -178,30 +225,23 @@ class DataLoggingPanel:
time_str = dt_object.strftime("%H:%M:%S")
result_text = f"Last query result: {count} aircraft at {time_str} UTC"
self.last_query_result_var.set(result_text)
except (ValueError, TypeError):
self.last_query_result_var.set("Last query result: Invalid timestamp")
except tk.TclError:
module_logger.warning(
"TclError updating last query label, widget likely destroyed."
)
except (ValueError, TypeError, tk.TclError):
pass
def clear_summary_table(self):
if not self.summary_table or not self.summary_table.winfo_exists():
return
self.last_query_result_var.set("Last query result: N/A")
self.session_stopped()
try:
for item in self.summary_table.get_children():
self.summary_table.delete(item)
module_logger.info("Data logging summary table cleared.")
except tk.TclError:
module_logger.warning(
"TclError clearing summary table, widget likely destroyed."
)
pass
def update_settings(self, enabled: bool, directory: str):
self.enable_logging_var.set(enabled)
self.log_directory_var.set(directory)
self._on_toggle_logging()
module_logger.debug(
f"DataLoggingPanel settings updated: enabled={enabled}, dir='{directory}'"
)
self._on_toggle_logging()

BIN
mappa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

66
test_overlay.py Normal file
View File

@ -0,0 +1,66 @@
import tkinter as tk
from PIL import Image, ImageDraw, ImageTk
class FlightSimulatorApp:
def __init__(self, root, map_path):
self.root = root
self.root.title("Simulatore Tracce Aeree")
# Carica mappa da file
original_map = Image.open(map_path).convert("RGB")
self.width, self.height = original_map.size
# Tkinter canvas
self.canvas = tk.Canvas(root, width=self.width, height=self.height)
self.canvas.pack()
# Mappa di sfondo
self.base_map = original_map
# Lista aerei
self.aircrafts = [
{"x": 100, "y": 100, "vx": 2, "vy": 1},
{"x": 200, "y": 250, "vx": 1, "vy": -1},
{"x": 300, "y": 400, "vx": -1, "vy": 2},
]
self.tracks = [[] for _ in self.aircrafts]
# Avvio animazione
self.update_frame()
def update_frame(self):
# Copia mappa
frame = self.base_map.copy()
draw = ImageDraw.Draw(frame)
# Disegna aerei e tracce
for i, aircraft in enumerate(self.aircrafts):
aircraft["x"] += aircraft["vx"]
aircraft["y"] += aircraft["vy"]
# Salva traccia
self.tracks[i].append((aircraft["x"], aircraft["y"]))
# Traccia gialla
if len(self.tracks[i]) > 1:
draw.line(self.tracks[i], fill='yellow', width=2)
# Aereo rosso
x, y = aircraft["x"], aircraft["y"]
draw.ellipse((x-5, y-5, x+5, y+5), fill='red')
# Aggiorna immagine Tkinter
self.tk_image = ImageTk.PhotoImage(frame)
self.canvas.create_image(0, 0, anchor='nw', image=self.tk_image)
# Frame successivo
self.root.after(100, self.update_frame)
if __name__ == "__main__":
# Percorso immagine di sfondo (mappa)
mappa_path = "mappa.png" # ← Cambia con il nome del tuo file
root = tk.Tk()
app = FlightSimulatorApp(root, mappa_path)
root.mainloop()