# Capitolo 2: Architettura Software e Moduli

[← Introduzione](01_Introduzione_e_Overview.md) | [Torna all'Indice](00_INDEX.md) | [Prossimo: Script Principale →](03_Script_Principale_GRIFO_M_PBIT.md)

---

## 2.1 Struttura delle Directory

Il repository è organizzato con una chiara separazione tra codice di test, framework, simulatore e documentazione:

```
SXXXXXXX_PlatSim/
│
├── TestEnvironment/                    # ⭐ Core Test Environment
│   ├── env/                           # Framework modules
│   │   ├── leo_grifo_1553.py         # 1553 bus interface
│   │   ├── leo_grifo_terminal.py     # Serial parsing
│   │   ├── leo_grifo_io_box.py       # Power control
│   │   ├── leo_grifo_core.py         # Core utilities
│   │   ├── leo_grifo_test_report.py  # PDF generation
│   │   ├── test_common_function.py   # Common API
│   │   ├── brainbox_interface.py     # BrainBox abstraction
│   │   └── site-packages/            # Dependencies (reportlab, etc.)
│   │
│   ├── scripts/                       # Test scripts
│   │   ├── GRIFO_M_PBIT.py          # ⭐ Main test script
│   │   ├── GRIFO_M_PBIT_mock.py     # Mock implementation
│   │   ├── GRIFO_M_PBIT_gui.py      # GUI monitor
│   │   └── __init__.py               # Path bootstrap
│   │
│   ├── json/                          # Test scenarios & templates
│   │   ├── scenarios_template.json   # Scenario definitions
│   │   └── test_report_template.json # PDF report structure
│   │
│   ├── pdf_reports/                   # 📄 Generated outputs
│   └── LOG/                           # Test execution logs
│
├── PlatformSimulator/                 # Hardware simulator
│   ├── bin/                          # Embedded Python + binaries
│   │   ├── python.exe                # Python 3.8 embedded
│   │   ├── interpreter.py            # SWIG wrapper
│   │   ├── _interpreter.pyd          # Native 1553 library
│   │   └── DLLs/                     # Python dependencies
│   │
│   └── LeonardoCompany/              # Native libraries
│       └── x86_64-w64-mingw32/
│
├── _doc/                              # 📚 Documentation
│   ├── GRIFO_M_PBIT_Documentation/   # This documentation
│   ├── IMPLEMENTATION_SUMMARY.md
│   ├── LAUNCHER_GUIDE.md
│   └── ...
│
├── tests/                             # Unit tests & debug scripts
│   ├── test_tgt_gen_implementation.py
│   └── test_lazy_proxy.py
│
└── *.bat                              # 🚀 Launcher scripts
    ├── run_target.bat
    ├── run_simulate.bat
    └── run_simulate_simple.bat
```

### 2.1.1 Motivazioni Architetturali

**Separazione `env/` e `scripts/`**:
- `env/`: Framework riutilizzabile per diversi test
- `scripts/`: Test specifici che usano il framework

**Python Embedded in `PlatformSimulator/`**:
- Permette esecuzione standalone senza installazione Python
- Include tutte le dipendenze (reportlab, pyserial)
- Garantisce versione Python consistente

**Bootstrap in `scripts/__init__.py`**:
```python
# Aggiunge ../env a sys.path per import diretti
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'env'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'env', 'site-packages'))
```

## 2.2 Moduli Core del Framework

### 2.2.1 `leo_grifo_1553.py` - Interfaccia Bus 1553

**Responsabilità**: Gestione comunicazione con radar via bus 1553 (MIL-STD-1553B)

**Classi Principali**:

```python
class GrifoInstrumentInterface:
    """
    Interfaccia di alto livello per comunicazione 1553.
    Wrappa il modulo interpreter (SWIG) con API Python-friendly.
    """
    
    def __init__(self, configuration_file: str):
        """Carica configurazione messaggi da file XML/JSON"""
        pass
    
    def check(self, RT: int, expected_value: Any, 
              msg_name: str, field_name: str) -> Tuple[bool, str]:
        """Verifica valore campo su messaggio 1553"""
        pass
    
    def get(self, RT: int, msg_name: str, field_name: str) -> Any:
        """Legge valore campo da messaggio 1553"""
        pass
    
    def set(self, RT: int, value: Any, msg_name: str, 
            field_name: str, commitChanges: bool = False) -> Tuple[bool, str]:
        """Scrive valore campo su messaggio 1553"""
        pass
    
    def enable_notification(self, RT: int, msg_name: str):
        """Abilita notifiche per messaggio specifico"""
        pass
```

**Singleton Globale**:
```python
# In modalità normale (hardware)
theGrifo1553 = GrifoInstrumentInterface("config_1553.xml")

# In modalità simulate (mock)
theGrifo1553 = MockGrifo1553Interface()  # Stesso API, comportamento simulato
```

**Messaggi 1553 Principali**:

| Messaggio | RT | Direzione | Scopo |
|-----------|-----|-----------|-------|
| `A1_MsgRdrSettingsAndParameters` | 1 | BC→RT | Configurazione radar (intensità, gain, ecc.) |
| `A2_MsgRdrOperationCommand` | 1 | BC→RT | Comandi operativi (mode, PBIT, STBY) |
| `A4_MsgNavDataAndCursor` | 1 | BC→RT | Dati navigazione (heading, posizione) |
| `A5_MsgInuHighSpeed` | 1 | BC→RT | Dati INU ad alta frequenza |
| `B6_MsgRdrSettingsAndParametersTellback` | 1 | RT→BC | Tellback configurazione e BIT status |
| `B8_MsgBitReport` | 1 | RT→BC | Report dettagliato BIT (field per ogni test) |
| `B9_MsgRdrTrackAndTargetsData` | 1 | RT→BC | Dati target rilevati (range, azimuth) |

**Esempio Utilizzo**:
```python
from leo_grifo_1553 import theGrifo1553

# Invia comando PBIT
ret, err = setValue(theGrifo1553, 1, "A2_MsgRdrOperationCommand", 
                    "a2_radarMode", commitChanges=True)

# Verifica completamento PBIT
ret, err = check(theGrifo1553, "false", "B6_MsgRdrSettingsAndParametersTellback", 
                 "b6_t1_rdr_fail_status")
```

### 2.2.2 `leo_grifo_terminal.py` - Parsing Messaggi Seriali

**Responsabilità**: Acquisizione e parsing messaggi dalla porta seriale del radar

**Classi Principali**:

```python
class SerialMessageParser:
    """
    Parser per messaggi seriali GRIFO.
    Riconosce pattern specifici (%%E, %%F, RECYCLE) e estrae dati.
    """
    
    def __init__(self):
        self.statistics = {
            'total_messages': 0,
            'error_messages': 0,    # %%E
            'fatal_messages': 0,    # %%F
            'recycle_events': 0     # RECYCLE detected
        }
    
    def parse_message(self, line: str) -> Dict[str, Any]:
        """Analizza singola riga seriale e estrae informazioni"""
        pass
    
    def extract_statistics(self) -> Dict[str, int]:
        """Ritorna statistiche aggregate messaggi"""
        pass
```

**Pattern Messaggi Riconosciuti**:

| Pattern | Significato | Azione |
|---------|-------------|--------|
| `%%E` | Error message | Incrementa `error_messages`, log con severità WARNING |
| `%%F` | Fatal error | Incrementa `fatal_messages`, log con severità ERROR |
| `RECYCLE` | Processor recycle event | Incrementa `recycle_events`, marca come evento critico |
| `PBIT COMPLETED` | PBIT concluso | Estrae tempo completamento |
| `BIT AVAILABLE` | BIT disponibile | Aggiorna stato disponibilità |

**Esempio Utilizzo**:
```python
from leo_grifo_terminal import SerialMessageParser

parser = SerialMessageParser()

# Durante test, per ogni riga seriale ricevuta
line = serial_port.readline().decode('utf-8')
msg_info = parser.parse_message(line)

# A fine test
stats = parser.extract_statistics()
print(f"Errors: {stats['error_messages']}, Fatal: {stats['fatal_messages']}")
```

### 2.2.3 `leo_grifo_io_box.py` - Controllo Alimentazione

**Responsabilità**: Gestione accensione/spegnimento radar tramite BrainBox

**Classi Principali**:

```python
class BrainBoxInterface:
    """
    Interfaccia per controllo alimentazione radar via BrainBox.
    """
    
    def __init__(self, port: str = "COM3"):
        """Inizializza connessione seriale con BrainBox"""
        pass
    
    def power_on(self, channel: int = 1) -> bool:
        """Accende canale specificato"""
        pass
    
    def power_off(self, channel: int = 1) -> bool:
        """Spegne canale specificato"""
        pass
    
    def get_status(self, channel: int = 1) -> str:
        """Legge stato corrente canale (ON/OFF)"""
        pass
```

**Singleton Globale**:
```python
theBrainBox = BrainBoxInterface(port="COM3")
```

**Funzioni Helper**:
```python
def power_grifo_on(wait_after: float = 2.0):
    """
    Accende radar e attende stabilizzazione.
    
    Args:
        wait_after: Secondi di attesa dopo accensione (default 2.0)
    """
    theBrainBox.power_on(channel=1)
    time.sleep(wait_after)

def power_grifo_off(wait_before: float = 1.0, wait_after: float = 5.0):
    """
    Spegne radar con timing appropriato.
    
    Args:
        wait_before: Secondi di attesa prima dello spegnimento
        wait_after: Secondi di attesa dopo spegnimento (per discharge condensatori)
    """
    time.sleep(wait_before)
    theBrainBox.power_off(channel=1)
    time.sleep(wait_after)
```

### 2.2.4 `leo_grifo_core.py` - Utilities e Recorder

**Responsabilità**: Funzioni di supporto e registrazione step di test

**Classi Principali**:

```python
class TestRecorder:
    """
    Registra step di test per generazione report finale.
    Pattern Singleton.
    """
    
    def __init__(self):
        self.steps = []
        self.session_data = {}
    
    def add_step(self, step_name: str, result: str, 
                 details: Dict[str, Any] = None):
        """Aggiunge step di test con risultato"""
        pass
    
    def close_session(self, overall_result: str):
        """Chiude sessione test e prepara dati per report"""
        pass
    
    def get_session_data(self) -> Dict[str, Any]:
        """Ritorna dati sessione per generazione report"""
        pass
```

**Singleton Globale**:
```python
theRecorder = TestRecorder()
```

**Funzioni Utility**:
```python
def timestamp_iso() -> str:
    """Ritorna timestamp ISO 8601 corrente"""
    return datetime.now().isoformat()

def format_duration(seconds: float) -> str:
    """Formatta durata in formato leggibile (es. "1h 23m 45s")"""
    pass

def safe_cast(value: Any, type_func: Callable) -> Optional[Any]:
    """Cast sicuro con gestione eccezioni"""
    try:
        return type_func(value)
    except (ValueError, TypeError):
        return None
```

### 2.2.5 `test_common_function.py` - API Comuni

**Responsabilità**: Fornisce API uniforme per interazione con tutti gli strumenti

**Funzioni Principali**:

```python
def check(instrument, expected_value, msg_name: str, 
          field_name: str) -> Tuple[bool, str]:
    """
    Verifica valore campo su qualsiasi strumento.
    Wrapper generico che delega a instrument.check().
    
    Returns:
        (success: bool, error_message: str)
    """
    return instrument.check(1, expected_value, msg_name, field_name)

def getValue(instrument, msg_name: str, field_name: str) -> Any:
    """
    Legge valore campo da qualsiasi strumento.
    """
    return instrument.get(1, msg_name, field_name)

def setValue(instrument, value: Any, msg_name: str, field_name: str,
             commitChanges: bool = False) -> Tuple[bool, str]:
    """
    Scrive valore campo su qualsiasi strumento.
    """
    return instrument.set(1, value, msg_name, field_name, commitChanges)
```

**Vantaggi dell'Astrazione**:
- Test code non dipende da implementazione specifica strumento
- Facilita mocking (basta sostituire singleton con mock che rispetta API)
- Permette logging centralizzato di tutte le operazioni

### 2.2.6 `leo_grifo_test_report.py` - Generazione PDF

**Responsabilità**: Generazione report PDF strutturati con tabelle e grafici

**Classi Principali**:

```python
class PDFClass:
    """
    Generatore report PDF usando ReportLab.
    """
    
    def __init__(self):
        self.pdf_buffer = None
        self.template = None
    
    def load_template(self, json_template_path: str):
        """Carica template JSON per struttura report"""
        pass
    
    def pdf_generate(self, json_test_data: Dict[str, Any]) -> str:
        """
        Genera PDF da dati test.
        
        Returns:
            path: Path del file PDF generato
        """
        pass
    
    def pdf_add_per_run_summary(self, runs_data: List[Dict]):
        """Aggiunge tabella riepilogo run"""
        pass
    
    def pdf_add_statistics_chart(self, stats: Dict):
        """Aggiunge grafico statistiche aggregate"""
        pass
```

**Singleton Globale**:
```python
testReport = PDFClass()
```

**Workflow Generazione**:
1. Test script popola dati in formato JSON
2. `testReport.load_template()` carica struttura report
3. `testReport.pdf_generate()` crea PDF con tabelle/grafici
4. File salvato in `TestEnvironment/pdf_reports/`

## 2.3 Diagramma delle Dipendenze

```
┌─────────────────────────────────────────────────────────────┐
│                    GRIFO_M_PBIT.py                          │
│                   (Test Orchestrator)                        │
└────────┬─────────────────────────────────────┬──────────────┘
         │                                     │
         │ imports                             │ imports
         ↓                                     ↓
┌─────────────────────┐              ┌────────────────────────┐
│ test_common_        │              │ GRIFO_M_PBIT_mock.py   │
│ function.py         │              │ (Simulation Logic)     │
│ - check()           │              └────────────────────────┘
│ - getValue()        │                         │
│ - setValue()        │                         │ replaces singletons
└──────┬──────────────┘                         ↓
       │                              ┌────────────────────────┐
       │ delegates                    │ Mock Objects           │
       ↓                              │ - MockGrifo1553        │
┌─────────────────────┐              │ - MockBrainBox         │
│ Singleton Instances │              └────────────────────────┘
│ - theGrifo1553      │←──────────────────────┘
│ - theBrainBox       │
│ - theRecorder       │
└──────┬──────────────┘
       │ implemented by
       ↓
┌──────────────────────────────────────────────────────────┐
│           Framework Modules (env/)                        │
├──────────────────┬───────────────────┬───────────────────┤
│ leo_grifo_       │ leo_grifo_        │ leo_grifo_        │
│ 1553.py          │ terminal.py       │ io_box.py         │
└──────┬───────────┴──────┬────────────┴──────┬────────────┘
       │                  │                   │
       │ uses             │ uses              │ uses
       ↓                  ↓                   ↓
┌──────────────────────────────────────────────────────────┐
│         Hardware Abstraction (bin/)                       │
├──────────────────┬───────────────────┬───────────────────┤
│ interpreter.py   │ pySerial          │ BrainBox Protocol │
│ (SWIG wrapper)   │                   │                   │
└──────┬───────────┴───────────────────┴───────────────────┘
       │
       │ calls native code
       ↓
┌──────────────────────────────────────────────────────────┐
│              Native Libraries                             │
│  - _interpreter.pyd (1553 driver DLL)                    │
└──────────────────────────────────────────────────────────┘
```

## 2.4 Pattern Architetturali

### 2.4.1 Singleton Pattern

**Motivazione**: Garantire unica istanza di interfacce hardware condivisa tra funzioni

**Implementazione**:
```python
# leo_grifo_1553.py
_instance = None

def get_grifo_1553_instance():
    global _instance
    if _instance is None:
        _instance = GrifoInstrumentInterface("config.xml")
    return _instance

theGrifo1553 = get_grifo_1553_instance()
```

**Pro**:
- Evita re-inizializzazioni costose (apertura porte, caricamento config)
- Stato condiviso (statistiche, buffer messaggi)

**Contro**:
- Testing complicato (stato globale)
- Risolto con monkey patching in modalità simulate

### 2.4.2 Facade Pattern

**Motivazione**: Semplificare interfaccia complessa 1553/seriale con API high-level

**Implementazione**: `test_common_function.py` fa da facade:
```python
# Invece di:
instrument.get(1, "B6_MsgRdrSettingsAndParametersTellback", "b6_t1_rdr_fail_status")

# User code usa:
getValue(theGrifo1553, "B6_MsgRdrSettingsAndParametersTellback", "b6_t1_rdr_fail_status")
```

### 2.4.3 Strategy Pattern (Simulation)

**Motivazione**: Intercambiare implementazione hardware/mock senza cambiare test code

**Implementazione**:
```python
# GRIFO_M_PBIT_mock.py definisce mock con stessa interfaccia
class MockGrifo1553Interface:
    def check(self, RT, expected, msg, field):
        # Logica simulata
        return (True, "")  # Simula successo
    
    def get(self, RT, msg, field):
        # Ritorna valore simulato basato su scenario
        return self._simulated_values.get(field, 0)

# In GRIFO_M_PBIT.py con --simulate
if args.simulate:
    from GRIFO_M_PBIT_mock import MockGrifo1553Interface, MockBrainBox
    theGrifo1553 = MockGrifo1553Interface()
    theBrainBox = MockBrainBox()
```

## 2.5 Gestione Dipendenze

### 2.5.1 Dipendenze Python

**Librerie Richieste**:
- `reportlab` (≥3.6): Generazione PDF
- `pyserial` (≥3.5): Comunicazione seriale
- `tkinter` (built-in): GUI

**Installazione**:
```bash
# Se usi Python di sistema
pip install reportlab pyserial

# In embedded Python (già incluse in site-packages/)
# Nessuna installazione necessaria
```

### 2.5.2 Dipendenze Native

**Librerie C/C++**:
- `_interpreter.pyd`: Driver 1553 compilato (DLL Windows)
- `PlatformSimulator/LeonardoCompany/`: Librerie native aggiuntive

**Binding SWIG**:
```python
# interpreter.py (generato automaticamente da SWIG)
import _interpreter  # Importa DLL nativa

class Msg1553:
    """Wrapper Python per classe C++ Msg1553"""
    def __init__(self, *args):
        self.this = _interpreter.new_Msg1553(*args)
```

### 2.5.3 Path Management

**Bootstrap in `scripts/__init__.py`**:
```python
# Eseguito automaticamente quando si importa da TestEnvironment/scripts/
import sys
import os

# Aggiunge ../env a sys.path
script_dir = os.path.dirname(__file__)
env_dir = os.path.abspath(os.path.join(script_dir, '..', 'env'))
site_packages = os.path.join(env_dir, 'site-packages')

sys.path.insert(0, env_dir)
sys.path.insert(0, site_packages)
```

**Risultato**: Import diretti funzionano ovunque:
```python
from leo_grifo_1553 import theGrifo1553  # ✅ Funziona
from test_common_function import check   # ✅ Funziona
```

## 2.6 Convenzioni di Naming

### 2.6.1 Moduli

- `leo_grifo_*`: Moduli framework specifici GRIFO
- `test_common_*`: Utilities generiche riutilizzabili

### 2.6.2 Singleton

- Pattern `the<Nome>`: `theGrifo1553`, `theBrainBox`, `theRecorder`
- Indica istanza globale unica

### 2.6.3 Messaggi 1553

- Prefisso `A<n>`: Messaggi BC→RT (comandi)
- Prefisso `B<n>`: Messaggi RT→BC (telemetria)
- Esempio: `A2_MsgRdrOperationCommand`, `B6_MsgRdrSettingsAndParametersTellback`

### 2.6.4 Campi Messaggi

- Formato `<msg_prefix>_<field_name>`: `b6_t1_rdr_fail_status`
- Underscore separatore
- Snake_case per tutti i field

## 2.7 Thread Safety e Concurrency

### 2.7.1 GUI Threading

**Problema**: GUI Tkinter deve girare su main thread, test su worker thread

**Soluzione**: Queue-based communication
```python
# GRIFO_M_PBIT_gui.py
class TestMonitorGUI:
    def __init__(self):
        self.update_queue = queue.Queue()  # Thread-safe
    
    def update_run(self, run, **kwargs):
        """Chiamato da test thread"""
        self.update_queue.put(('run_row', {'run': run, ...}))
    
    def _poll_updates(self):
        """Eseguito su main thread (Tkinter)"""
        while not self.update_queue.empty():
            update_type, data = self.update_queue.get_nowait()
            self._apply_update(update_type, data)
        
        self.root.after(100, self._poll_updates)  # Re-schedule
```

### 2.7.2 Hardware Access

**Nota**: Accesso hardware NON è thread-safe
- Tutti i comandi 1553/seriale da singolo thread (test thread)
- GUI legge solo dati processati (no accesso diretto hardware)

---

[← Introduzione](01_Introduzione_e_Overview.md) | [Torna all'Indice](00_INDEX.md) | [Prossimo: Script Principale →](03_Script_Principale_GRIFO_M_PBIT.md)
