From 4995c0743dbdc4618dd9c21c07ece607a1943fed Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 29 Oct 2025 12:27:37 +0100 Subject: [PATCH] aggiunto il rate dei pacchetti inviati dal server --- .github/copilot-instructions.md | 96 +++++++++++-------- .../analysis/simulation_state_hub.py | 39 ++++++++ target_simulator/gui/main_view.py | 19 +++- target_simulator/gui/payload_router.py | 9 ++ 4 files changed, 118 insertions(+), 45 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f52f2f7..f6e7b57 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,52 +1,64 @@ -# Copilot Instructions for Target Simulator +## Copilot / AI agent instructions — Target Simulator (concise) -## Project Overview -- **Target Simulator** is a Python desktop application (Tkinter GUI) for simulating radar targets and scenarios, with communication via TFTP and serial protocols. -- Main entry: `target_simulator/__main__.py` launches the GUI (`MainView`). -- Core logic is split into: - - `core/`: simulation engine, communication interfaces, scenario models - - `gui/`: Tkinter windows, frames, and widgets - - `utils/`: logging, config management, TFTP client +This file contains the minimal, repository-specific guidance an AI coding agent needs to be productive. -## Architecture & Data Flow -- **Simulation** runs in a background thread (`SimulationEngine`) and updates target states/scenarios. -- **Communication**: Supports both TFTP and serial, abstracted via `CommunicatorInterface` and implemented in `serial_communicator.py` and `tftp_communicator.py`. -- **Configuration**: Settings and scenarios are stored in `settings.json` (read/write via `ConfigManager`). -- **Logging**: Centralized, routed to both console and GUI (`logger.py`). GUI log widget uses colored levels. -- **Scenarios**: Defined in `settings.json` under `scenarios`, loaded and managed via GUI and core logic. +1) Big picture +- Type: Python desktop app (Tkinter) that simulates radar targets and communicates with hardware/emulators via SFP/TFTP/Serial. +- Major areas: + - `target_simulator/core/` — simulation, command builders, communicator interfaces (CommunicatorInterface is the contract). + - `target_simulator/gui/` — Tkinter windows; `MainView` is the app host and coordinates simulation + communicators. + - `target_simulator/utils/` — `ConfigManager`, logging helpers (`utils/logger.py`), CSV trace helpers. -## Developer Workflows -- **Run app**: `python -m target_simulator` (from project root) -- **Debug**: Use Python debugger on `__main__.py` or GUI modules -- **Settings**: Edit `settings.json` for global config, connection, and scenario data -- **Logging**: Adjust `LOGGING_CONFIG` in `config.py` for format/colors +2) Key workflows & commands (PowerShell) +- Run app (development): + $env:PYTHONPATH='C:\src\____GitProjects\target_simulator'; python -m target_simulator +- Open REPL with project path: + $env:PYTHONPATH='C:\src\____GitProjects\target_simulator'; python +- Run tests (coverage task available): use the workspace task labeled "pytest coverage report" or run directly: + $env:PYTHONPATH='C:\src\____GitProjects\target_simulator'; pytest --cov=target_simulator.core.models --cov-report=term-missing -## Patterns & Conventions -- **Absolute imports** are used throughout (e.g., `from target_simulator.core...`). -- **PEP8** style, English for code/comments/docstrings -- **One statement per line**; concise, essential comments only -- **Modularization**: If a file grows >1000 lines, split by topic into new modules -- **Scenario/Settings**: All persistent config is managed via `ConfigManager` and stored in `settings.json` -- **GUI**: All Tkinter windows/frames are in `gui/`, main window is `MainView` -- **Logging**: Use `get_logger`, `setup_basic_logging`, and `add_tkinter_handler` for consistent logging +3) Project-specific patterns & conventions +- Absolute imports are used throughout; `__main__.py` inserts the project root into `sys.path` for convenience. +- Persistence: `ConfigManager` stores general settings in `settings.json` and scenarios in `scenarios.json` (atomic write + rotating backups `.bak1`, `.bak2`). +- Logging: central queue-based logging that forwards to console/file/GUI. Use `setup_basic_logging(app, LOGGING_CONFIG)` and `add_tkinter_handler(widget, LOGGING_CONFIG)`; temporary increases via `temporary_log_level(logger, level)`. +- Simulation: `SimulationEngine` runs as a daemon thread and emits GUI updates via a Queue; do not change update semantics lightly — runtime sends `tgtset` without qualifiers while debug GUI may include qualifiers. +- Communicator contract: implement `CommunicatorInterface` (methods like `connect(config)->bool`, `disconnect()`, `send_commands(list)`, property `is_open`). `SFP` communicator supports deferred connect and an optional `_use_json_protocol` flag (checked by engine). -## Integration Points -- **External dependencies**: Tkinter, threading, TFTP/serial (no third-party packages required) -- **Temp files**: Written to `Temp/` for live updates and scenario init logs -- **C++ code**: `BupTFTP.cpp/h` present but not integrated with Python app +4) Important implementation details to preserve +- Debug vs runtime sending: the SFP debug window intentionally sends `tgtset` with state qualifiers (Active/Traceable/Restart) while the live `SimulationEngine` uses `tgtset` without qualifiers to avoid changing lifecycle flags. See `gui/sfp_debug_window.py` and `core/command_builder.py` for the builders. +- Scenarios are saved to a separate `scenarios.json`; `ConfigManager` will avoid overwriting it with empty data and performs atomic replace + rotating backups. +- `MainView._setup_communicator` shows how new communicator types are wired (update this method when adding a protocol). -## Examples -- To add a new communication protocol, implement `CommunicatorInterface` in `core/` and update GUI logic in `main_view.py`. -- To add a new scenario, update `settings.json` and use `ConfigManager` methods. -- To extend logging, add new handlers in `logger.py` and update `config.py` for color/format. +5) Integration points & external artifacts +- Native libs: Tkinter (GUI). No heavy third-party dependencies are required for core features. +- Optional C++ helpers exist in `cpp/` (e.g., `BupTFTP.cpp`) but are not integrated by default. +- Temporary CSV logs and traces live in `Temp/` (filenames configured in `settings.json` `debug` block). -## References -- Main entry: `target_simulator/__main__.py` -- Simulation: `core/simulation_engine.py` -- GUI: `gui/main_view.py`, other `gui/` modules -- Config: `utils/config_manager.py`, `settings.json` -- Logging: `utils/logger.py`, `config.py` +6) Files to inspect for common tasks (quick links) +- App start: `target_simulator/__main__.py` +- Main UI and lifecycle: `target_simulator/gui/main_view.py` +- Simulation loop and sending logic: `target_simulator/core/simulation_engine.py` +- Command builders: `target_simulator/core/command_builder.py` +- Communicators: `target_simulator/core/*.py` (look for `SFP`, `Serial`, `TFTP` implementations) +- Persistence: `target_simulator/utils/config_manager.py` and `settings.json` / `scenarios.json` +- Logging helpers: `target_simulator/utils/logger.py` and `logger_prefs.json` + +7) Small examples (copyable snippets) +- Enable module debug at runtime (REPL): + import logging; logging.getLogger('target_simulator.gui.sfp_debug_window').setLevel(logging.DEBUG) +- Start app (PowerShell): + $env:PYTHONPATH='C:\src\____GitProjects\target_simulator'; python -m target_simulator + +8) When changing behavior +- If you change how commands are formatted (e.g., `build_tgtset_*`), update both the SimulationEngine logic and the SFP debug sender to keep debug vs runtime semantics clear. +- If you add a new persistent key, prefer storing it under `general` in `settings.json` via `ConfigManager.save_general_settings()`. + +9) What the agent should *not* do automatically +- Do not change scenario storage semantics (moving scenarios back into `settings.json`), since `ConfigManager` intentionally separates scenarios and performs backups. + +10) If more detail is needed +- Ask which area to expand (GUI wiring, communicator API, command builders, or logging). Provide the specific file and a brief goal and the agent will produce patches and tests. --- -If any section is unclear or missing, please provide feedback to improve these instructions. \ No newline at end of file +If anything here is unclear or you want additional examples (unit tests, small refactors, or a runtime checklist), tell me which sections to expand and I will iterate. \ No newline at end of file diff --git a/target_simulator/analysis/simulation_state_hub.py b/target_simulator/analysis/simulation_state_hub.py index df798b8..88282f0 100644 --- a/target_simulator/analysis/simulation_state_hub.py +++ b/target_simulator/analysis/simulation_state_hub.py @@ -46,6 +46,13 @@ class SimulationStateHub: # Timestamps (monotonic) of recent "real" events for rate computation # Keep a bounded deque to avoid unbounded memory growth. self._real_event_timestamps = collections.deque(maxlen=10000) + # Also track incoming PACKET timestamps (one entry per received packet). + # Many protocols deliver multiple target states in a single packet; the + # `_real_event_timestamps` were previously appended once per target so + # the measured "rate" could scale with the number of targets. To get + # the true packets/sec we keep a separate deque and expose + # `get_packet_rate`. + self._real_packet_timestamps = collections.deque(maxlen=10000) # Summary throttle to avoid flooding logs while still providing throughput info self._last_real_summary_time = time.monotonic() self._real_summary_interval_s = 1.0 @@ -175,6 +182,38 @@ class SimulationStateHub: except Exception: return 0.0 + def add_real_packet(self, timestamp: Optional[float] = None): + """ + Record the arrival of a packet (containing potentially many target updates). + + Args: + timestamp: optional monotonic timestamp to use (defaults to now). + """ + try: + ts = float(timestamp) if timestamp is not None else time.monotonic() + self._real_packet_timestamps.append(ts) + except Exception: + # silently ignore errors -- non-critical + pass + + def get_packet_rate(self, window_seconds: float = 1.0) -> float: + """ + Returns an approximate packets/sec rate based on recorded packet arrival + timestamps over the last `window_seconds`. + """ + try: + now = time.monotonic() + cutoff = now - float(window_seconds) + count = 0 + for ts in reversed(self._real_packet_timestamps): + if ts >= cutoff: + count += 1 + else: + break + return count / float(window_seconds) if window_seconds > 0 else float(count) + except Exception: + return 0.0 + def set_real_heading( self, target_id: int, heading_deg: float, raw_value: float = None ): diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 9f8d1ee..f88f0a0 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -1056,14 +1056,19 @@ class MainView(tk.Tk): """ try: real_rate = 0.0 + packet_rate = 0.0 ppi_rate = 0.0 try: if self.simulation_hub and hasattr( self.simulation_hub, "get_real_rate" ): real_rate = float(self.simulation_hub.get_real_rate(1.0)) + # Prefer packet rate if available (packets/sec vs events/sec) + if self.simulation_hub and hasattr(self.simulation_hub, "get_packet_rate"): + packet_rate = float(self.simulation_hub.get_packet_rate(1.0)) except Exception: real_rate = 0.0 + packet_rate = 0.0 try: if ( @@ -1077,9 +1082,17 @@ class MainView(tk.Tk): if getattr(self, "rate_status_var", None) is not None: try: - self.rate_status_var.set( - f"real in: {real_rate:.1f} ev/s | ppi upd: {ppi_rate:.1f} upd/s" - ) + # Show both packet-level rate and per-target event rate so + # users can distinguish network throughput (packets/sec) + # from per-target updates (events/sec). + if packet_rate > 0.0: + self.rate_status_var.set( + f"pkt in: {packet_rate:.1f} pkt/s | ev in: {real_rate:.1f} ev/s | ppi upd: {ppi_rate:.1f} upd/s" + ) + else: + self.rate_status_var.set( + f"real in: {real_rate:.1f} ev/s | ppi upd: {ppi_rate:.1f} upd/s" + ) except Exception: pass except Exception: diff --git a/target_simulator/gui/payload_router.py b/target_simulator/gui/payload_router.py index 6357aa0..d576259 100644 --- a/target_simulator/gui/payload_router.py +++ b/target_simulator/gui/payload_router.py @@ -155,6 +155,15 @@ class DebugPayloadRouter: # to ensure simulated and real data can be correlated. reception_timestamp = time.monotonic() + # Record a single packet-level arrival timestamp so callers can + # measure packets/sec (instead of per-target events/sec). + try: + if hasattr(self._hub, "add_real_packet"): + self._hub.add_real_packet(reception_timestamp) + except Exception: + # Non-fatal: continue even if packet recording fails + pass + # Add real states for active targets for target in real_targets: state_tuple = (