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

@ -297,6 +297,16 @@ class AppController:
self.live_adapter_thread.start() self.live_adapter_thread.start()
self.live_data_processor.start_processing_queue() 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: if self.aircraft_db_manager and self._active_bounding_box:
session_params = { session_params = {
"start_time": time.time(), "start_time": time.time(),
@ -321,6 +331,14 @@ class AppController:
module_logger.info( module_logger.info(
f"Controller: Stopping live monitoring (from_error={from_error})." 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 self.is_live_monitoring_active = False
if ( if (
@ -381,6 +399,18 @@ class AppController:
panel.add_summary_entry(timestamp, aircraft_count) panel.add_summary_entry(timestamp, aircraft_count)
panel.update_last_query_result(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): def open_log_directory(self, directory_path: str):
if not directory_path: if not directory_path:
self.main_window.show_error_message("Error", "No directory path specified.") self.main_window.show_error_message("Error", "No directory path specified.")
@ -495,6 +525,17 @@ class AppController:
) )
self.historical_adapter_thread.start() self.historical_adapter_thread.start()
self.historical_data_processor.start_processing() 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( self.main_window.update_semaphore_and_status(
GUI_STATUS_FETCHING, "Historical download started." GUI_STATUS_FETCHING, "Historical download started."
) )
@ -524,6 +565,13 @@ class AppController:
self._current_scan_params, self._active_bounding_box, status 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.is_historical_download_active = False
self._current_scan_params = None 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 import tkinter as tk
from tkinter import ttk, filedialog, font as tkFont from tkinter import ttk, filedialog, font as tkFont
from typing import Optional, Any 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 from flightmonitor.utils.logger import get_logger
@ -29,17 +30,22 @@ class DataLoggingPanel:
self.parent_frame = parent_frame self.parent_frame = parent_frame
self.controller = controller 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 --- # --- Tkinter Variables ---
self.enable_logging_var = tk.BooleanVar(value=False) self.enable_logging_var = tk.BooleanVar(value=False)
self.log_directory_var = tk.StringVar(value="") self.log_directory_var = tk.StringVar(value="")
self.last_query_result_var = tk.StringVar(value="Last query result: N/A") 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 --- # --- Widget References ---
self.log_dir_entry: Optional[ttk.Entry] = None self.log_dir_entry: Optional[ttk.Entry] = None
self.browse_button: Optional[ttk.Button] = None self.browse_button: Optional[ttk.Button] = None
self.open_folder_button: Optional[ttk.Button] = ( self.open_folder_button: Optional[ttk.Button] = None
None # MODIFICATO: Aggiunto riferimento
)
self.summary_table: Optional[ttk.Treeview] = None self.summary_table: Optional[ttk.Treeview] = None
self._build_ui() self._build_ui()
@ -72,7 +78,6 @@ class DataLoggingPanel:
) )
self.log_dir_entry.grid(row=0, column=1, sticky=tk.EW) self.log_dir_entry.grid(row=0, column=1, sticky=tk.EW)
# MODIFICATO: Aggiunto il nuovo pulsante "Open Folder"
self.browse_button = ttk.Button( self.browse_button = ttk.Button(
dir_frame, dir_frame,
text="Browse...", text="Browse...",
@ -88,16 +93,23 @@ class DataLoggingPanel:
state=tk.DISABLED, state=tk.DISABLED,
) )
self.open_folder_button.grid(row=0, column=3, sticky=tk.E, padx=(2, 0)) 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( last_result_label = ttk.Label(
container, container,
textvariable=self.last_query_result_var, textvariable=self.last_query_result_var,
font=last_result_font, font=info_font,
foreground="navy", 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 = ttk.Frame(container)
table_frame.pack(fill=tk.BOTH, expand=True, pady=(2, 0)) 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) self.log_dir_entry.config(state=new_state)
if self.browse_button: if self.browse_button:
self.browse_button.config(state=new_state) self.browse_button.config(state=new_state)
# MODIFICATO: Abilita/disabilita anche il nuovo pulsante
if self.open_folder_button: if self.open_folder_button:
self.open_folder_button.config(state=new_state) 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): def _on_browse_directory(self):
directory = filedialog.askdirectory( directory = filedialog.askdirectory(
@ -138,18 +146,62 @@ class DataLoggingPanel:
) )
if directory: if directory:
self.log_directory_var.set(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): def _on_open_folder(self):
"""Callback for the 'Open Folder' button."""
directory_path = self.get_log_directory() directory_path = self.get_log_directory()
if self.controller and hasattr(self.controller, "open_log_directory"): if self.controller and hasattr(self.controller, "open_log_directory"):
self.controller.open_log_directory(directory_path) self.controller.open_log_directory(directory_path)
else:
module_logger.error( def session_started(self, start_timestamp: float, query_rate_sec: Optional[int]):
"Controller or 'open_log_directory' method not available." """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: def is_logging_enabled(self) -> bool:
return self.enable_logging_var.get() return self.enable_logging_var.get()
@ -160,17 +212,12 @@ class DataLoggingPanel:
def add_summary_entry(self, timestamp: float, count: int): def add_summary_entry(self, timestamp: float, count: int):
if not self.summary_table or not self.summary_table.winfo_exists(): if not self.summary_table or not self.summary_table.winfo_exists():
return return
try: try:
dt_object = datetime.fromtimestamp(timestamp, timezone.utc) dt_object = datetime.fromtimestamp(timestamp, timezone.utc)
time_str = dt_object.strftime("%Y-%m-%d %H:%M:%S") time_str = dt_object.strftime("%Y-%m-%d %H:%M:%S")
self.summary_table.insert("", 0, values=(time_str, count)) self.summary_table.insert("", 0, values=(time_str, count))
except (ValueError, TypeError) as e: except (ValueError, TypeError, tk.TclError):
module_logger.error(f"Failed to add summary entry to table: {e}") pass
except tk.TclError:
module_logger.warning(
"TclError adding summary to table, widget likely destroyed."
)
def update_last_query_result(self, timestamp: float, count: int): def update_last_query_result(self, timestamp: float, count: int):
try: try:
@ -178,30 +225,23 @@ class DataLoggingPanel:
time_str = dt_object.strftime("%H:%M:%S") time_str = dt_object.strftime("%H:%M:%S")
result_text = f"Last query result: {count} aircraft at {time_str} UTC" result_text = f"Last query result: {count} aircraft at {time_str} UTC"
self.last_query_result_var.set(result_text) self.last_query_result_var.set(result_text)
except (ValueError, TypeError): except (ValueError, TypeError, tk.TclError):
self.last_query_result_var.set("Last query result: Invalid timestamp") pass
except tk.TclError:
module_logger.warning(
"TclError updating last query label, widget likely destroyed."
)
def clear_summary_table(self): def clear_summary_table(self):
if not self.summary_table or not self.summary_table.winfo_exists(): if not self.summary_table or not self.summary_table.winfo_exists():
return return
self.last_query_result_var.set("Last query result: N/A") self.last_query_result_var.set("Last query result: N/A")
self.session_stopped()
try: try:
for item in self.summary_table.get_children(): for item in self.summary_table.get_children():
self.summary_table.delete(item) self.summary_table.delete(item)
module_logger.info("Data logging summary table cleared.")
except tk.TclError: except tk.TclError:
module_logger.warning( pass
"TclError clearing summary table, widget likely destroyed."
)
def update_settings(self, enabled: bool, directory: str): def update_settings(self, enabled: bool, directory: str):
self.enable_logging_var.set(enabled) self.enable_logging_var.set(enabled)
self.log_directory_var.set(directory) self.log_directory_var.set(directory)
self._on_toggle_logging() self._on_toggle_logging()
module_logger.debug(
f"DataLoggingPanel settings updated: enabled={enabled}, dir='{directory}'"
)

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()