# Piano di Migrazione: GrifoScope → PyBusMonitor1553

## 📊 Situazione Attuale

### Sistema a 3 Componenti

```
┌─────────────────┐         UDP 1553          ┌─────────────────┐
│   GrifoScope    │◄────────────────────────►│  Radar Server   │
│  (BusMonitor)   │   Port 50553 ← 60553     │  (Target Real)  │
│    C++/Qt       │                           │                 │
└─────────────────┘                           └─────────────────┘
        ▲
        │ DA SOSTITUIRE CON
        ▼
┌─────────────────┐         UDP 1553          ┌─────────────────┐
│PyBusMonitor1553 │◄────────────────────────►│  Radar Server   │
│  (BusMonitor)   │   Port 51553 ← 61553     │  (Target Real)  │
│     Python      │                           │                 │
└─────────────────┘                           └─────────────────┘
```

**Obiettivo**: Far funzionare PyBusMonitor1553 esattamente come GrifoScope per comunicare con il Radar Server.

---

## 🔍 Analisi del Comportamento di GrifoScope

### 1. **Architettura di Comunicazione** (`qg1553overudp.cpp`)

GrifoScope usa un pattern **Bus Controller (BC)** che:

#### **Inizializzazione** (`Qg1553OverUdp::Implementation::BusGenerator()`)
```cpp
// Bind sulla porta locale 60553 (60000 + 1553)
s.bind(networkInterface(), 60000+1553);

// Indirizzo destinazione: broadcast o IP specifico radar
rtAddress = networkBroadcast(); // Es: 127.0.0.255

// Registra messaggi A (BC→RT, tr=0):
addMsg<msg_rdr_settings_and_parameters_t>(1, 0);  // A1
addMsg<msg_rdr_operation_command_t>(2, 0);        // A2
addMsg<msg_graphic_setting_t>(3, 0);              // A3
addMsg<msg_nav_data_and_cursor_t>(4, 0);          // A4
addMsg<msg_inu_high_speed_t>(5, 0);               // A5

// Registra messaggi B (RT→BC, tr=1):
addMsg<msg_settings_tellback_t>(15, 1);           // B6
addMsg<msg_rdr_status_tellback_t>(16, 1);         // B7
```

**CRITICO**: GrifoScope invia **datagrammi multi-messaggio**:
```cpp
struct b1553_datagram_t {
    uint16_t number_of_messages;
    b1553_raw_message_t msgs[20];  // Fino a 20 messaggi in un frame
};
```

#### **Trasmissione Ciclica** (`doWork()`)
```cpp
// Timer a 10 secondi (10000 µs) per invio frame completo
scheduler->start(10000);  // 100 Hz

// Ogni ciclo invia TUTTI i messaggi registrati in UN solo UDP datagram
s.writeDatagram(&msg, sizeof(msg), rtAddress, 50000+1553);
```

**Differenza Critica con PyBusMonitor**:
- GrifoScope → **1 datagram UDP** con **tutti i messaggi A1-A5** + request B6-B7
- PyBusMonitor → **multi datagram** (uno per messaggio) con scheduler separato

---

### 2. **Formato Pacchetto UDP1553** (`udp1553_types.h`, `b1553_udp.cpp`)

#### **Header UDP1553** (64 bytes - già implementato ✅)
```cpp
struct udp_1553_header_t {
    uint16_t marker1553;     // 0x1553
    uint8_t vmajor, vminor;  // Version
    uint16_t otype;          // 0x4342 ("BC") o 0x5452 ("RT")
    uint8_t ta;              // Terminal Address (0=BC, 20=RT)
    uint8_t flags;
    uint32_t fcounter;       // Frame counter
    uint32_t mcounter;       // Master counter
    // ... altri campi timing/debug
};
```

#### **Blocco Messaggio** (ripetuto N volte)
```
0x3C3C          // MARKER_BEGIN
CW              // Command Word (RT, TR, SA, WC)
SW              // Status Word (solo RT replies)
ERRCODE         // Error code
reserved[2]     // 8 bytes reserved
DATA[wc]        // Payload (wc words, Big Endian)
~CW             // Inverted command word
0x3E3E          // MARKER_END
```

#### **Terminatore Frame**
```
0x5315          // MARKER_END_1553
```

**⚠️ Problema Rilevato nel Dispatcher**: 
Il parser PyBusMonitor ha già logica per multi-messaggio MA ha bug con terminatori (vedi `dispatcher.py` linee 180-220). GrifoScope usa layout fisso.

---

### 3. **Sequenza di Startup Radar** (`g346_a1a2.cpp`)

GrifoScope implementa sequenza di sicurezza **obbligatoria**:

```cpp
void GrifoMsg_A2B7::startProtection() {
    dSilence.setFromUser(1);  // RF SILENCE = ON
    dStby.setFromUser(1);     // STANDBY = ON
}
```

**Workflow Startup** (già parzialmente implementato ✅):

| Fase | Durata | STANDBY | SILENCE | Azione |
|------|--------|---------|---------|--------|
| **1 - Protection** | 2-3 cicli | ON | ON | Stato sicuro, radar non risponde |
| **2 - Activation** | 1 ciclo | OFF | OFF | Comando attivazione |
| **3 - Transition** | 6 cicli (~240ms) | OFF | OFF | Radar elabora cambio modo |
| **4 - Operational** | continuo | OFF | OFF | Monitoraggio B7 `transition_status` |

**Implementazione Python**: Vedi `RadarController.apply_startup_sequence()` - **già corretto** ✅

---

### 4. **Gestione Request/Reply** (`b1553_udp.cpp`)

GrifoScope usa pattern **request-response**:

```cpp
// INVIO (BC → RT):
// - A1-A5: Comandi con tr=0, payload pieno
// - B6-B7: Richieste con tr=1, payload vuoto (solo CW)

// RICEZIONE (RT → BC):
// Radar risponde con stesso SA, tr=1, payload pieno
if (cw.str.tr == 1) {  // RT→BC reply
    // Copia dati nel buffer locale
    memcpy(local_buffer[sa], data, wc*2);
}
```

**Ciclo Tipico**:
1. GrifoScope invia frame con A1-A5 (comandi) + B6-B7 (request vuote)
2. Radar risponde con frame separato contenente B6-B7 (dati pieni)
3. GrifoScope aggiorna display con tellback B7

**PyBusMonitor Status**: 
- ✅ Invia A-messages (comandi)
- ⚠️ Invia B-requests MA non usa formato corretto (vedi sotto)

---

## 🔧 Differenze Critiche da Risolvere

**Riepilogo**:
- ❌ **2 problemi CRITICI** da risolvere (Multi-Message, B-Request)
- ✅ **3 aspetti già corretti** (Porte, Endianness, Startup)

---

### ❌ **PROBLEMA 1: Formato Multi-Messaggio**

**GrifoScope**:
```
UDP Header (64 bytes)
  ↓
0x3C3C | CW_A1 | ... | DATA_A1[32] | ~CW_A1 | 0x3E3E
0x3C3C | CW_A2 | ... | DATA_A2[32] | ~CW_A2 | 0x3E3E
0x3C3C | CW_A3 | ... | DATA_A3[32] | ~CW_A3 | 0x3E3E
0x3C3C | CW_A4 | ... | DATA_A4[32] | ~CW_A4 | 0x3E3E
0x3C3C | CW_A5 | ... | DATA_A5[32] | ~CW_A5 | 0x3E3E
0x3C3C | CW_B6 | ... | [EMPTY]     | ~CW_B6 | 0x3E3E  ← REQUEST
0x3C3C | CW_B7 | ... | [EMPTY]     | ~CW_B7 | 0x3E3E  ← REQUEST
0x5315  (END marker)
```

**PyBusMonitor Attuale** (`scheduler.py`):
- Invia messaggi A/B in frame separati
- Usa rate diversi (50Hz, 25Hz, 6.25Hz)
- Non raggruppa i messaggi

**Radar potrebbe aspettarsi frame singolo con tutti i messaggi sincronizzati!**

---

### ❌ **PROBLEMA 2: B-Messages Request Format**

**GrifoScope** per richieste B (RT→BC):
```cpp
// B6/B7 request: tr=1, wc=32, payload=0 bytes (solo header)
addMsg<msg_settings_tellback_t>(15, 1);  // SA=15, tr=1, wc=32
```

**PyBusMonitor Attuale**:
```python
# B-messages hanno payload pieno (32 words) anche in TX
self.msg_b7 = msgs.MsgB7()  # Crea con _data[32] inizializzato
```

**ERRORE**: B-requests dovrebbero avere:
- `tr = 1` (RT transmit) ✅ 
- `wc = 32` ✅
- **Payload VUOTO** (0 bytes data) ❌ → **Attualmente 64 bytes**

---

### ✅ **~~PROBLEMA 3: Porte UDP~~ - RISOLTO**

**VERIFICATO**: Le porte sono già correttamente allineate!

**GrifoScope**: 60000+1553 = **61553** (RX), 50000+1553 = **51553** (TX)  
**PyBusMonitor**: RX_PORT = **61553**, TARGET_PORT = **51553**

✅ **Nessuna modifica necessaria**

---

### ✅ **~~PROBLEMA 4: Endianness e Bit Numbering~~ - GIÀ CORRETTO**

**VERIFICATO**:
- ✅ Payload: Big Endian (network byte order) - **OK**
- ✅ Headers: Little Endian - **OK**
- ✅ Bit numbering: MSB=0 convention - **OK nei field descriptors**

**CRITICO da verificare**: 
- Inverted CW (`~cw`) deve usare **bitwise NOT** su 16-bit
- Alcuni radar potrebbero essere sensibili a byte swap in `~CW`

---

### ⚠️ **PROBLEMA 5: Porte UDP - FALSO ALLARME** ✅

**VERIFICATO**: Le porte sono già allineate correttamente!

**Da verificare con test reale**: 
- Inverted CW (`~cw`) usa bitwise NOT su 16-bit (già implementato ✅)
- Layout terminatore: `inv_cw (LE) → MARKER_END (LE)` (già implementato ✅)
❌ ~~Discrepanza di 1000 porte~~ → ✅ **IDENTICHE - Nessun problema**

---

### ⚠️ **PROBLEMA 6: Dispatcher Multi-Message Bug**

File `dispatcher.py` linee 180-250 contiene workaround per terminatori:

```python
# Accept if LE matches expected and control marker is correct
if (inv_cw_le == expected_inv_cw) and (ctrl_le == MARKER_END):
    accepted = True
# Else try BE, fallback scans, etc...
```

**Issue**: Logica troppo permissiva causa falsi positivi. GrifoScope usa **layout fisso**:
```
DATA[wc] → inv_cw (LE) → MARKER_END (LE)
```

---

## ✅ Cosa Funziona Già (Non Toccare)

1. **Message Definitions** (`lib1553/messages/`) - ✅ Corrette vs C++ ICD
2. **Field Descriptors** (`fields.py`) - ✅ MSB=0 bit numbering
3. **PacketBuilder** (`packet_builder.py`) - ✅ UDP1553 header format
4. **Startup Sequence** (`controller.py`) - ✅ STANDBY/SILENCE phases
5. **Navigation Data** - ✅ Validity flags (inverse logic)

---

## 🎯 Piano d'Azione

### **FASE 1: Verifica Compatibilità Formato** (Diagnostica)

**Obiettivo**: Catturare traffico GrifoScope reale e confrontare con PyBusMonitor

#### Task 1.1: Cattura Pacchetti GrifoScope
```bash
# Usa Wireshark o tcpdump per catturare:
# - GrifoScope → Radar (porta 50553)
# - Radar → GrifoScope (porta 60553)

# Salva PCAP e analizza:
python tools/analyze_wireshark_dump.py grifoscope_capture.pcap
```

**Deliverable**: File con layout esatto frame GrifoScope

#### Task 1.2: Confronto Binario
```python
# tools/compare_grifoscope_packets.py
# Compara:
# - Header fields (fcounter, mcounter, etc.)
# - Message order (A1-A5, B6-B7)
# - Word count per messaggio
# - Payload content (campo per campo)
```

**Deliverable**: Report discrepanze

---

### **FASE 2: Fix Formato B-Request** (Codice)

**Problema**: B-messages inviate con payload pieno invece di vuoto

#### Task 2.1: Modificare PacketBuilder
```python
# pybusmonitor1553/core/packet_builder.py

def build_message_block(self, message, force_empty_payload=False):
    """
    Args:
        force_empty_payload: Se True, invia solo header senza data
                             (usato per B-requests)
    """
    if force_empty_payload:
        # tr=1, wc=32 MA data_bytes=0
        return header_bytes + inv_cw + marker_end
    else:
        # tr=0 o tr=1 con payload
        return header_bytes + data_bytes + inv_cw + marker_end
```

#### Task 2.2: Flag nelle Message Class
```python
# lib1553/message_base.py

class MessageBase:
    IS_TRANSMIT = False  # Existing
    REQUEST_MODE = False  # NEW: True per B-requests (no payload)
    
# lib1553/messages/msg_b7.py
class MsgB7(MessageBase):
    IS_TRANSMIT = True
    REQUEST_MODE = True  # ← B-messages sono requests!
```

**Deliverable**: B-requests inviate con 0 data bytes

---

### **FASE 3: Implementare Frame Multi-Messaggio** (Architettura)

**Problema**: Inviare tutti i messaggi in UN frame come GrifoScope

#### Task 3.1: Nuovo Builder Pattern
```python
# pybusmonitor1553/core/packet_builder.py

def build_multi_message_frame(self, messages):
    """
    Costruisce frame singolo con N messaggi (stile GrifoScope).
    
    Args:
        messages: Lista [MsgA1, MsgA2, ..., MsgB7]
    Returns:
        bytes: UDP datagram completo
    """
    frame = UDP1553Header(...)  # 64 bytes
    
    for msg in messages:
        # Determina se request (B-msg) o command (A-msg)
        is_request = getattr(msg, 'REQUEST_MODE', False)
        frame += build_message_block(msg, force_empty_payload=is_request)
    
    frame += MARKER_END_1553
    return frame
```

#### Task 3.2: Modificare Scheduler
```python
# pybusmonitor1553/core/scheduler.py

class TrafficScheduler:
    def __init__(self, controller, network):
        self.mode = 'GRIFOSCOPE'  # NEW: 'LEGACY' o 'GRIFOSCOPE'
    
    def _schedule_grifoscope_mode(self):
        """
        Invia frame singolo con tutti i messaggi ogni 40ms (25 Hz).
        """
        messages = [
            self.controller.msg_a1,
            self.controller.msg_a2,
            self.controller.msg_a3,
            self.controller.msg_a4,
            self.controller.msg_a5,
            self.controller.msg_b6,  # Request
            self.controller.msg_b7,  # Request
        ]
        frame = self.builder.build_multi_message_frame(messages)
        self.network.send(frame, self.target_ip, self.target_port)
```

**Deliverable**: Modalità compatibile GrifoScope attivabile via flag

---

### **FASE 4: Fix Dispatcher Multi-Message** (Parser)

**Problema**: Parser troppo permissivo con terminatori

#### Task 4.1: Semplificare Logica
```python
# pybusmonitor1553/core/dispatcher.py

def parse_packet(self, raw_data):
    # REMOVE: Fallback scans, BE/LE alternation
    # USE: Strict layout matching GrifoScope
    
    # Expected: DATA → inv_cw (LE) → MARKER_END (LE)
    inv_cw, ctrl_marker = struct.unpack_from('<HH', raw_data, offset)
    
    if inv_cw != expected_inv_cw:
        raise ValueError(f"Invalid ~CW at offset {offset}")
    if ctrl_marker != MARKER_END:
        raise ValueError(f"Invalid MARKER_END at offset {offset}")
```Network Configuration** (Config) - ✅ OPZIONALE

#### Task 5.1: ~~Porte UDP~~ - GIÀ CORRETTE ✅
Le porte sono identiche a GrifoScope. Nessuna modifica necessaria.

#### Task 5.2: Broadcast vs Unicast (Opzionale)
```python
# Verificare con test reale se radar richiede broadcast
# GrifoScope usa: rtAddress = networkBroadcast()
# Può essere 127.0.0.255 (broadcast) o 127.0.0.1 (unicast)

BROADCAST_MODE = os.getenv('PYBM_BROADCAST', 'false').lower() == 'true'

if BROADCAST_MODE:
    TARGET_IP = "127.0.0.255"  # Broadcast locale
else:
    TARGET_IP = "127.0.0.1"    # Unicast (default attuale)
```

**Deliverable**: Test con radar reale determinerà necessità
    TARGET_IP = "127.0.0.255"  # Broadcast locale
else:
    TARGET_IP = "127.0.0.1"
```

**Deliverable**: Flag ambiente per compatibilità totale

---

### **FASE 6: Testing con Radar Reale** (Validazione)

#### Task 6.1: Test di Connessione
```bash
# 1. Stop GrifoScope
# 2. Avvia PyBusMonitor in GRIFOSCOPE_MODE
export PYBM_GRIFOSCOPE_MODE=true
python -m pybusmonitor1553

# 3. Verifica:
# - Radar risponde con B-messages?
# - Transition status in B7 arriva a OPERATIONAL?
# - Display radar mostra parametri corretti?
```

#### Task 6.2: Confronto Comportamentale
| Metrica | GrifoScope | PyBusMonitor | Status |
|---------|-----------|--------------|--------|
| Radar risponde | ✅ SI | ❓ DA VERIFICARE | |
| B7 transition complete | ✅ 6 cicli | ❓ DA VERIFICARE | |
| Parametri radar aggiornati | ✅ SI | ❓ DA VERIFICARE | |
| Frame rate | 100 Hz | 25 Hz → 100 Hz | TODO |

**Deliverable**: Report test con radar vivo

---

## 📁 Struttura Modifiche al Codice

```
pybusmonitor1553/
├── core/
│   ├── packet_builder.py         # ✏️ MODIFY: Multi-message frame
│   ├── scheduler.py              # ✏️ MODIFY: GRIFOSCOPE mode
│   ├── dispatcher.py             # ✏️ FIX: Strict terminator parsing
│   └── controller.py             # ✅ OK (startup sequence già corretto)
├── lib1553/
│   ├── message_base.py           # ✏️ ADD: REQUEST_MODE flag
│   └── messages/
│       ├── msg_b*.py             # ✏️ SET: REQUEST_MODE = True
└── __main__.py                   # ✏️ ADD: GRIFOSCOPE_MODE env var

tools/
├── compare_grifoscope_packets.py # 🆕 NEW: Binary comparison
├── analyze_wireshark_dump.py    # 🆕 NEW: PCAP parser
└── simple_rt_responder.py        # ✅ OK (test tool)

doc/
└── GrifoScope-Migration-Plan.md  # 📄 THIS FILE
```

---

## 🚦 Roadmap Implementazione

### **Sprint 1 (Settimana 1): Diagnostica**
- [ ] Cattura traffico GrifoScope con Wireshark
- [ ] Analizza formato esatto frame
- [ ] Identifica differenze vs PyBusMonitor
- [ ] Scrivi tool `compare_grifoscope_packets.py`

### **Sprint 2 (Settimana 2): Fix Formato**
- [ ] Implementa `REQUEST_MODE` flag
- [ ] Modifica `PacketBuilder.build_message_block()`
- [ ] Test con `simple_rt_responder.py`
- [ ] Verifica B-requests inviate senza payload

### **Sprint 3 (Settimana 3): Multi-Message**
- [ ] Implementa `build_multi_message_frame()`
- [ ] Aggiungi `GRIFOSCOPE` mode a scheduler
- [ ] Test frame rate (25 Hz vs 100 Hz)
- [ ] Benchmark performance

### **Sprint 4 (Settimana 4): Integration**
- [ ] Fix dispatcher strict parsing
- [ ] Allinea porte (60553/50553)
- [ ] Test con radar simulato
- [ ] ⭐ **TEST CON RADAR REALE**

### **Sprint 5 (Settimana 5): Hardening**
- [ ] Handle edge cases (timeout, packet loss)
- [ ] Logging migliorato per debug
- [ ] Documentazione operativa
- [ ] Training team su modalità GrifoScope

---

## ⚠️ Rischi e Mitigazioni

| Rischio | Probabilità | Impatto | Mitigazione |
|---------|-------------|---------|-------------|
| Radar hardcoded su porte GrifoScope | Alta | Alto | Usare stesse porte in GRIFOSCOPE_MODE |
| Frame format incompatibile | Media | Alto | Cattura PCAP prima di modifiche |
| ~~Porte UDP diverse~~ | ✅ RISOLTO | - | Porte già allineate (61553/51553) |
| Frame format incompatibile | Media | Alto | Cattura PCAP prima di modifiche |
| Timing critico (100 Hz) | Media | Medio | Benchmark Python threading performance |
| Radar richiede broadcast IP | Bassa | Medio | Test con 127.0.0.255 vs 127.0.0.1 |
| Radar richiede checksum/CRC | Bassa | Alto | Analizza header `errcode` field

## 📊 Metriche di Successo

### **Criteri di Accettazione**

✅ **MUST HAVE**:
1. Radar risponde ai comandi A-messages
2. Radar invia B-messages di tellback
3. Transition status raggiunge OPERATIONAL
4. Zero crash o errori di parsing

✅ **SHOULD HAVE**:
5. Frame rate = 100 Hz (come GrifoScope)
6. Latenza < 10ms per ciclo
7. Compatibilità backward con modalità legacy

✅ **NICE TO HAVE**:
8. GUI mostra differenze GrifoScope vs PyBusMonitor
9. Tool di diagnostica automatico
10. Documentazione completa workflow

---

## 🔗 Riferimenti

### **File Chiave C++**
- `cpp/GrifoScope/GrifoScope/QgExt/qg1553overudp.cpp` - Logica trasmissione
- `cpp/GrifoScope/GrifoSdkEif/dev/Portable/ext/b1553_udp.cpp` - Parser radar-side
- `cpp/GrifoScope/GrifoMCS/GADS/MCS/MCS_G346/g346/g346_a1a2.cpp` - Startup sequence

### **File Chiave Python**
- `pybusmonitor1553/core/dispatcher.py` - Parser UDP1553
- `pybusmonitor1553/core/packet_builder.py` - Builder frame
- `pybusmonitor1553/core/scheduler.py` - Timing messaggi
- `pybusmonitor1553/core/controller.py` - Startup radar

### **Documentazione**
- `doc/ICD-Implementation-Notes.md` - Discrepanze ICD vs C++
- `doc/Technical-Architecture.md` - Architettura sistema
- `.github/copilot-instructions.md` - Regole sviluppo

---

## 🎓 Note per il Team

### **Approccio Incrementale**
1. **Non stravolgere l'architettura esistente** - Aggiungi modalità compatibilità
2. **Mantieni backward compatibility** - Flag `GRIFOSCOPE_MODE` opzionale
3. **Test continuo** - Ogni modifica deve passare pytest + test manuale
4. **Cattura prima, modifica dopo** - PCAP è la fonte di verità

### **Debug Tips**
```bash
# Abilita logging verbose
export PYBM_LOG_LEVEL=DEBUG

# Cattura pacchetti in tempo reale
python tools/network_sniffer.py --source python --debug

# Confronta con GrifoScope
diff captured_a_messages_commands_grifoscope.txt \
     captured_a_messages_commands_python.txt
```

### **Quando Chiamare Aiuto**
- ❌ Radar non risponde dopo 3 cicli di debug
- ❌ Crash sistematico su parse multi-message
- ❌ Performance < 25 Hz su hardware target
- ❌ Discrepanze ICD non documentate nel C++

---

## ✨ Conclusioni

**Status Attuale**: PyBusMonitor ha ~80% delle funzionalità necessarie. 

**Gap Principali**:
1. ❌ Frame multi-messaggio (vs singoli datagram)
2. ❌ B-requests con payload vuoto
3. ⚠️ Porte UDP diverse

**Effort Stimato**: 
- Fase 1-2: 1 settimana (diagnostica **85%** delle funzionalità necessarie. 

**Gap Principali** (solo 2!):
1. ❌ Frame multi-messaggio (vs singoli datagram)
2. ❌ B-requests con payload vuoto

**Già Corretti** ✅:
- ✅ Porte UDP (61553/51553 come GrifoScope)
- ✅ Endianness (Big Endian payload, Little Endian header)
- ✅ Startup sequence (STANDBY/SILENCE)
- ✅ Field descriptors (MSB=0 bit numbering)

**Effort Stimato** (rivisto): 
- Fase 1-2: 1 settimana (diagnostica + B-request fix)
- Fase 3-4: 1.5 settimane (multi-message + dispatcher)
- Fase 5-6: 0.5 settimane (test radar + validazione)

**Total**: ~**3 settimane** per compatibilità completa ⚡

**Priorità**: 
1. **CRITICA** - B-request format (blocca risposte radar)
2. **ALTA** - Multi-message frame (timing/formato)
3. **MEDIA** - Dispatcher cleanup (funziona ma migliorabile)
4. **BASSA** - Broadcast IP (test opziona

**Domande?** → Apri issue su GitHub o discuti con team lead.

---

*Documento creato: 2025-12-15*  
*Versione: 1.0*  
*Autore: AI Assistant + Team PyBusMonitor*
