663 lines
20 KiB
Markdown
663 lines
20 KiB
Markdown
# PyBusMonitor1553 - Architettura Tecnica e Scelte Implementative
|
|
|
|
**Documento di riferimento per sviluppatori**
|
|
*Versione: 1.0*
|
|
*Data: Dicembre 2025*
|
|
|
|
---
|
|
|
|
## Indice
|
|
1. [Panoramica Architetturale](#panoramica-architetturale)
|
|
2. [Sistema di Messaggi e Field Descriptors](#sistema-di-messaggi-e-field-descriptors)
|
|
3. [Protocollo UDP1553](#protocollo-udp1553)
|
|
4. [Scheduler Multi-Rate](#scheduler-multi-rate)
|
|
5. [Disaccoppiamento GUI/Motore](#disaccoppiamento-gui-motore)
|
|
6. [Threading e Sicurezza](#threading-e-sicurezza)
|
|
7. [Testing e Debug](#testing-e-debug)
|
|
|
|
---
|
|
|
|
## 1. Panoramica Architetturale
|
|
|
|
### 1.1 Struttura a Livelli
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ GUI Layer (Tkinter) │
|
|
│ main_window.py + gui.py │
|
|
└────────────────┬────────────────────────────────┘
|
|
│ Queue-based Updates
|
|
┌────────────────▼────────────────────────────────┐
|
|
│ Core Logic Layer │
|
|
│ RadarController + TrafficScheduler │
|
|
└────────┬──────────────────┬─────────────────────┘
|
|
│ │
|
|
│ │ UDP Network
|
|
▼ ▼
|
|
┌─────────────────┐ ┌──────────────────┐
|
|
│ MessageDispatcher│ │ UdpHandler │
|
|
│ (RX Parser) │ │ (Network I/O) │
|
|
└────────┬────────┘ └──────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Protocol Layer (lib1553) │
|
|
│ MessageBase + Field Descriptors + Constants │
|
|
└─────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 1.2 Flusso Dati
|
|
|
|
**Ricezione (RT→BC):**
|
|
```
|
|
UDP Socket → UdpHandler.receive() → MessageDispatcher.parse_packet()
|
|
→ Message Objects → GUI Update Queue → Periodic GUI Refresh
|
|
```
|
|
|
|
**Trasmissione (BC→RT):**
|
|
```
|
|
RadarController.msg_aX → TrafficScheduler (multi-rate) → PacketBuilder
|
|
→ UdpHandler.send() → UDP Socket
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Sistema di Messaggi e Field Descriptors
|
|
|
|
### 2.1 Perché i Descriptors?
|
|
|
|
**Problema**: I messaggi MIL-STD-1553 sono strutture a livello bit complesse. Gestire manualmente bit shifting, maschere e scaling è soggetto a errori.
|
|
|
|
**Soluzione**: Uso di Python Descriptors per:
|
|
- **Dichiarativo**: Definire campi in modo leggibile
|
|
- **Type-safe**: Validazione automatica tramite Enum
|
|
- **Scalabile**: Facile aggiungere nuovi messaggi
|
|
- **Manutenibile**: ICD e codice allineati
|
|
|
|
### 2.2 Implementazione Field Descriptors
|
|
|
|
**File**: `lib1553/fields.py`
|
|
|
|
```python
|
|
class BitField:
|
|
# Gestisce conversione MSB-0 (1553) → LSB-0 (Python)
|
|
# Esempio: Bit 0 (MSB) con width=1 → shift=15
|
|
|
|
class EnumField:
|
|
# Mapping automatico int ↔ IntEnum
|
|
# Gestisce valori non definiti (reserved)
|
|
|
|
class ScaledField:
|
|
# Conversione raw → physical con LSB
|
|
# Supporta signed (2's complement)
|
|
# Esempio: raw=100, lsb=0.5 → value=50.0
|
|
```
|
|
|
|
**Convenzione Bit Numbering**:
|
|
- **ICD MIL-STD-1553**: Bit 0 = MSB (Most Significant Bit)
|
|
- **Python**: Bit 0 = LSB (Least Significant Bit)
|
|
- **Field base class**: Gestisce automaticamente la conversione con `_shift = 16 - (start_bit + width)`
|
|
|
|
### 2.3 Esempio Pratico
|
|
|
|
```python
|
|
class MsgA2(MessageBase):
|
|
# ICD: Word 01, Bits 0-3 = Master Mode
|
|
master_mode = EnumField(
|
|
word_index=0, # Primo word (0-based)
|
|
start_bit=0, # MSB del word (convenzione 1553)
|
|
width=4, # 4 bit = 16 valori possibili
|
|
enum_cls=RadarMode
|
|
)
|
|
```
|
|
|
|
Internamente:
|
|
1. `_shift = 16 - (0 + 4) = 12`
|
|
2. `_mask = (1 << 4) - 1 = 0x0F`
|
|
3. `get: (word >> 12) & 0x0F`
|
|
4. `set: word = (word & ~(0x0F << 12)) | ((value & 0x0F) << 12)`
|
|
|
|
---
|
|
|
|
## 3. Protocollo UDP1553
|
|
|
|
### 3.1 Struttura Frame
|
|
|
|
```
|
|
┌──────────────────────────────────────────┐
|
|
│ UDP Header (64 bytes, Little Endian) │
|
|
│ - marker1553 = 0x1553 │
|
|
│ - fcounter, mcounter, etc. │
|
|
├──────────────────────────────────────────┤
|
|
│ Message Block 1: │
|
|
│ - Begin Marker (0x3C3C) │
|
|
│ - Command Word │
|
|
│ - Status Word │
|
|
│ - Error Code │
|
|
│ - Data (0-64 bytes, Big Endian!) │
|
|
│ - ~CW (inverted command word) │
|
|
│ - End Marker (0x3E3E) │
|
|
├──────────────────────────────────────────┤
|
|
│ Message Block 2... │
|
|
├──────────────────────────────────────────┤
|
|
│ End Marker (0x5315) │
|
|
└──────────────────────────────────────────┘
|
|
```
|
|
|
|
### 3.2 Endianness Critico!
|
|
|
|
**Perché due endianness diverse?**
|
|
|
|
1. **UDP Header (Little Endian)**:
|
|
- Usa `ctypes.Structure` che segue host endianness (x86 = Little)
|
|
- Contatori e metadati gestiti dal BC/RT locale
|
|
|
|
2. **Message Payload (Big Endian - Network Byte Order)**:
|
|
- Standard MIL-STD-1553 usa Big Endian
|
|
- Compatibilità con hardware avionico
|
|
- `MessageBase.pack()`: `struct.pack(">32H", ...)` (nota il `>`)
|
|
- `MessageBase.unpack()`: `struct.unpack(">32H", ...)`
|
|
|
|
**Errore comune**: Usare Little Endian per il payload → dati scrambled!
|
|
|
|
### 3.3 Command Word Bitfield Layout
|
|
|
|
```c
|
|
// Layout C/C++ (Little Endian bitfield packing)
|
|
struct CommandWord {
|
|
uint16_t word_count : 5; // Bits 0-4
|
|
uint16_t subaddress : 5; // Bits 5-9
|
|
uint16_t tr_bit : 1; // Bit 10
|
|
uint16_t remote_terminal : 5; // Bits 11-15
|
|
};
|
|
```
|
|
|
|
**Nota**: In Python usiamo `ctypes.c_uint16` con bitfield per compatibilità esatta.
|
|
|
|
---
|
|
|
|
## 4. Scheduler Multi-Rate
|
|
|
|
### 4.1 Problema Originale
|
|
|
|
Versione iniziale: tutti i messaggi a 1 Hz (debug).
|
|
|
|
**Requisito ICD**:
|
|
- A1, A3, A7, A8, B6: **6.25 Hz** (160ms)
|
|
- A2, B7: **25 Hz** (40ms)
|
|
- A4, A5: **50 Hz** (20ms)
|
|
|
|
### 4.2 Soluzione: Tick-Based Scheduling
|
|
|
|
**Base Tick Rate**: 200 Hz (5ms) - divisore comune di tutti i rate.
|
|
|
|
```
|
|
Rate Period Ticks/Message Divisor
|
|
50 Hz 20ms 4 ticks 4
|
|
25 Hz 40ms 8 ticks 8
|
|
6.25Hz 160ms 32 ticks 32
|
|
```
|
|
|
|
**Algoritmo**:
|
|
```python
|
|
for tick in infinite_loop:
|
|
for (msg, divisor) in schedule_table:
|
|
if tick % divisor == 0:
|
|
send(msg)
|
|
tick += 1
|
|
sleep_until_next_tick() # Timing preciso con perf_counter
|
|
```
|
|
|
|
### 4.3 Raggruppamento Frame
|
|
|
|
I messaggi che cadono nello stesso tick vengono **raggruppati in un unico frame UDP**:
|
|
- Riduce overhead di rete
|
|
- Mantiene atomicità temporale
|
|
- Esempio: Tick 8 → [A2, A4, A5, B7] in un frame
|
|
|
|
### 4.4 Timing Preciso
|
|
|
|
Usa `time.perf_counter()` invece di `time.sleep()` naïve:
|
|
|
|
```python
|
|
next_tick_time = time.perf_counter()
|
|
while running:
|
|
# ... send messages ...
|
|
next_tick_time += BASE_TICK_PERIOD # 5ms
|
|
sleep_time = next_tick_time - time.perf_counter()
|
|
if sleep_time > 0:
|
|
time.sleep(sleep_time)
|
|
else:
|
|
# In ritardo, resync
|
|
next_tick_time = time.perf_counter()
|
|
```
|
|
|
|
**Vantaggi**:
|
|
- Drift temporale minimizzato
|
|
- Jitter < 1ms su sistema non real-time
|
|
- Auto-recupero se il sistema rallenta
|
|
|
|
---
|
|
|
|
## 5. Disaccoppiamento GUI/Motore
|
|
|
|
### 5.1 Problema Originale
|
|
|
|
**Sintomo**: GUI progressivamente meno reattiva fino al blocco.
|
|
|
|
**Causa**:
|
|
- Ogni messaggio generava `root.after(0, callback)`
|
|
- A 50Hz → 50+ eventi/secondo nella coda Tkinter
|
|
- Accumulo di migliaia di eventi pending → freeze
|
|
|
|
### 5.2 Soluzione: Queue + Rate Limiting
|
|
|
|
**Pattern Producer-Consumer**:
|
|
|
|
```
|
|
Network Thread (Producer) GUI Thread (Consumer)
|
|
│ │
|
|
├─ on_packet() │
|
|
│ └─ queue.put_nowait() │
|
|
│ │
|
|
│ ┌────▼─────┐
|
|
│ │ Timer │
|
|
│ │ 100ms │
|
|
│ └────┬─────┘
|
|
│ │
|
|
│ ┌─────────▼────────────┐
|
|
│ │ _process_queue() │
|
|
│ │ - Batch max 50 msgs │
|
|
│ │ - Update TreeView │
|
|
│ │ - Update Details │
|
|
│ └──────────────────────┘
|
|
```
|
|
|
|
**Implementazione** (`gui/main_window.py`):
|
|
|
|
```python
|
|
class BusMonitorApp:
|
|
def __init__(self):
|
|
self._update_queue = queue.Queue(maxsize=1000)
|
|
self._status_queue = queue.Queue(maxsize=100)
|
|
self._gui_update_interval_ms = 100 # Rate limiting
|
|
self._start_gui_update_timer()
|
|
|
|
def queue_message_update(self, msg_name, msg_obj, raw_words):
|
|
"""Thread-safe: chiamato da network thread"""
|
|
try:
|
|
self._update_queue.put_nowait((msg_name, msg_obj, raw_words))
|
|
except queue.Full:
|
|
pass # Scarta se pieno, non bloccare!
|
|
|
|
def _process_update_queue(self):
|
|
"""Chiamato SOLO dal thread GUI ogni 100ms"""
|
|
# Batch processing: max 50 aggiornamenti/ciclo
|
|
for _ in range(50):
|
|
try:
|
|
msg_name, msg_obj, raw_words = self._update_queue.get_nowait()
|
|
self._update_message_stats_internal(msg_name, msg_obj, raw_words)
|
|
except queue.Empty:
|
|
break
|
|
|
|
# Riprogramma prossimo ciclo
|
|
self._update_id = self.root.after(100, self._process_update_queue)
|
|
```
|
|
|
|
### 5.3 Ottimizzazione Detail View
|
|
|
|
**Problema**: Ricostruire tutta la vista dettagli ogni aggiornamento è costoso.
|
|
|
|
**Soluzione**: Cached Widget Pattern
|
|
|
|
```python
|
|
# Prima selezione di un messaggio
|
|
if self._current_detail_message != msg_name:
|
|
self._build_detail_table(msg_name, msg_obj) # Costruisce layout completo
|
|
self._current_detail_message = msg_name
|
|
|
|
# Aggiornamenti successivi (VELOCE)
|
|
self._update_detail_values(msg_name, msg_obj) # Solo label.config()
|
|
```
|
|
|
|
**Struttura Dati**:
|
|
```python
|
|
_detail_widgets_cache = {
|
|
'A1': {
|
|
'master_mode': (raw_label, decoded_label, field_desc),
|
|
'symbol_intensity': (raw_label, decoded_label, field_desc),
|
|
# ...
|
|
}
|
|
}
|
|
```
|
|
|
|
**Vantaggi**:
|
|
- ✅ **10-100x più veloce**: Solo update testo invece di create/pack widgets
|
|
- ✅ **Meno flicker**: Layout stabile
|
|
- ✅ **Memoria minima**: Cache solo riferimenti ai widget
|
|
|
|
### 5.4 Tabella Dettagli Messaggi
|
|
|
|
Layout fisso con 3 colonne:
|
|
|
|
| Field Name | Raw Value | Decoded Value |
|
|
|-----------------|-----------|------------------------|
|
|
| master_mode | 0x03 | TWS (3) |
|
|
| symbol_intensity| 0x7F | 127 |
|
|
|
|
- **Colonna 1**: Nome campo (fisso, creato una volta)
|
|
- **Colonna 2**: Valore raw in hex (aggiornato ogni messaggio)
|
|
- **Colonna 3**: Valore decodificato (Enum name, float, etc.)
|
|
|
|
### 5.5 Vantaggi Architetturali
|
|
|
|
✅ **Performance costante**: GUI aggiorna a 10Hz fisso, indipendente dal traffico
|
|
✅ **No overflow**: Queue limitata, scarta invece di bloccare
|
|
✅ **Batch efficiency**: Riduce chiamate a Tkinter (costose)
|
|
✅ **Thread-safe**: Separazione completa network/GUI
|
|
✅ **Widget caching**: Detail view ottimizzato con layout fisso
|
|
|
|
### 5.6 Overflow Handling
|
|
|
|
Se i messaggi arrivano a >100Hz (10 msg/100ms):
|
|
1. Queue si riempie (1000 elementi)
|
|
2. `put_nowait()` lancia `queue.Full`
|
|
3. Exception catturata → messaggio scartato
|
|
4. **Nessun blocco**, GUI continua a funzionare
|
|
|
|
Accettabile perché:
|
|
- Statistiche (count, period) rimangono accurate
|
|
- Dettagli mostrano sempre ultimo valore valido
|
|
- Alternative peggiori: freeze o crash
|
|
|
|
---
|
|
|
|
## 6. Threading e Sicurezza
|
|
|
|
### 6.1 Thread Attivi
|
|
|
|
1. **Main Thread**: GUI Tkinter (MUST be main thread)
|
|
2. **Network RX Thread**: `UdpHandler._receive_loop()` (daemon)
|
|
3. **Scheduler TX Thread**: `TrafficScheduler._loop()` (daemon)
|
|
|
|
### 6.2 Regole di Sicurezza
|
|
|
|
**CRITICO**: Tkinter NON è thread-safe!
|
|
|
|
❌ **MAI fare**:
|
|
```python
|
|
# Da network thread
|
|
def on_packet(data):
|
|
app.msg_tree.item(...) # CRASH!
|
|
```
|
|
|
|
✅ **Sempre fare**:
|
|
```python
|
|
# Da network thread
|
|
def on_packet(data):
|
|
app.queue_message_update(...) # Thread-safe queue
|
|
|
|
# GUI thread (via timer)
|
|
def _process_queue():
|
|
app.msg_tree.item(...) # OK!
|
|
```
|
|
|
|
### 6.3 Lock e Sincronizzazione
|
|
|
|
**Non serve Lock esplicito** perché:
|
|
- `queue.Queue` è già thread-safe
|
|
- GUI thread è l'unico a modificare widget
|
|
- Network thread solo accodamento
|
|
|
|
**Eccezione**: Se si aggiunge scrittura condivisa su `RadarController`, serve `threading.Lock`.
|
|
|
|
---
|
|
|
|
## 7. Testing e Debug
|
|
|
|
### 7.1 Debug Flags
|
|
|
|
File `core/network.py`:
|
|
```python
|
|
DEBUG_PACKETS = False # Hex dump di tutti i pacchetti
|
|
```
|
|
|
|
File `core/scheduler.py`:
|
|
```python
|
|
DEBUG_PACKETS = False # Hex dump frame inviati
|
|
QUIET_MODE = False # Sopprimi log (per GUI)
|
|
```
|
|
|
|
File `core/packet_builder.py`:
|
|
```python
|
|
DEBUG_BUILD = False # Debug costruzione frame
|
|
```
|
|
|
|
### 7.2 Testing Pattern
|
|
|
|
**Unit Test** (`tests/test_fields.py`):
|
|
```python
|
|
class DummyMsg(MessageBase):
|
|
field = BitField(word_index=0, start_bit=0, width=8)
|
|
|
|
def test_bitfield():
|
|
msg = DummyMsg()
|
|
msg.field = 255
|
|
assert msg.field == 255
|
|
assert msg._data[0] == (255 << 8) # MSB position
|
|
```
|
|
|
|
**Integration Test** (`tests/test_dispatcher.py`):
|
|
```python
|
|
def test_parse_a1_packet():
|
|
# Build ctypes structures
|
|
udp_hdr = UDP1553Header()
|
|
cw = CommandWordUnion(rt_addr=20, sub_addr=1, ...)
|
|
msg_hdr = UDP1553MessageHeader(cw)
|
|
|
|
# Pack to bytes
|
|
raw = bytes(udp_hdr) + bytes(msg_hdr) + data_payload
|
|
|
|
# Parse
|
|
header, msg = dispatcher.parse_packet(raw)
|
|
assert msg.__class__.__name__ == "MsgA1"
|
|
```
|
|
|
|
### 7.3 Environment Variables
|
|
|
|
Configurazione network senza modificare codice:
|
|
|
|
```bash
|
|
# Windows PowerShell
|
|
$env:PYBM_RX_IP="192.168.1.100"
|
|
$env:PYBM_RX_PORT="61553"
|
|
$env:PYBM_TARGET_IP="192.168.1.200"
|
|
$env:PYBM_TARGET_PORT="51553"
|
|
python -m pybusmonitor1553
|
|
```
|
|
|
|
### 7.4 Strumenti Diagnostici
|
|
|
|
**UDP Diagnostic Tool** (`tools/udp_diagnostic.py`):
|
|
- Invia pacchetti di test
|
|
- Verifica connettività
|
|
- Dump binario dei frame
|
|
|
|
**Coverage Report**:
|
|
```bash
|
|
pytest --cov=pybusmonitor1553 --cov-report=html
|
|
# Apri htmlcov/index.html
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Best Practices per Nuovi Sviluppatori
|
|
|
|
### 8.1 Configurazione Iniziale Radar
|
|
|
|
**CRITICO**: Il radar GRIFO-F/TH richiede configurazione iniziale specifica per uscire da standby:
|
|
|
|
```python
|
|
# A2: Command - DEVE avere standby = OFF
|
|
msg_a2.standby_cmd = StandbyStatus.OFF # CRITICAL!
|
|
msg_a2.master_mode = RadarMode.RWS
|
|
msg_a2.range_scale = RangeScale.SCALE_20
|
|
msg_a2.bar_scan = BarScan.BAR_1
|
|
msg_a2.azimuth_scan = AzimuthScan.AZ_60
|
|
|
|
# A4: Nav Data - Validity flags = 0 significa VALIDO (logica inversa!)
|
|
msg_a4.nav_data_invalid = 0 # 0 = DATA VALID
|
|
msg_a4.baro_inertial_alt = 10000.0 # Fornire dati plausibili
|
|
msg_a4.tas = 300.0
|
|
```
|
|
|
|
**Nota**: I bit di validità hanno logica inversa: `0 = VALID`, `1 = INVALID`.
|
|
|
|
### 8.2 Aggiungere un Nuovo Messaggio
|
|
|
|
1. **Creare** `lib1553/messages/msg_xx.py`:
|
|
```python
|
|
class MsgXX(MessageBase):
|
|
SUBADDRESS = Subaddress.XX
|
|
IS_TRANSMIT = True/False
|
|
RATE_HZ = 25.0
|
|
|
|
field_name = EnumField(word_index=0, start_bit=0, width=4, enum_cls=MyEnum)
|
|
```
|
|
|
|
2. **Registrare** in `lib1553/messages/__init__.py`:
|
|
```python
|
|
from .msg_xx import MsgXX
|
|
```
|
|
|
|
3. **Aggiungere al dispatcher** in `core/dispatcher.py`:
|
|
```python
|
|
msgs.MsgXX, # nella lista message_classes
|
|
```
|
|
|
|
4. **Aggiungere allo scheduler** se BC→RT:
|
|
```python
|
|
(self.controller.msg_xx, 25.0), # in _build_schedule_table
|
|
```
|
|
|
|
### 8.2 Debugging Checklist
|
|
|
|
- [ ] `DEBUG_PACKETS = True` per vedere hex dump
|
|
- [ ] Verificare endianness: payload deve essere Big Endian
|
|
- [ ] Testare field con valori min/max/invalid
|
|
- [ ] Controllare bit numbering (MSB=0 in ICD)
|
|
- [ ] Verificare RATE_HZ e scheduling
|
|
|
|
### 8.3 Convenzioni Codice
|
|
|
|
- **Naming**: `snake_case` per tutto (Python PEP8)
|
|
- **Enums**: Sempre `IntEnum` in `constants.py`
|
|
- **Comments**: Referenziare ICD (es. "Ref. Tab. 7.1.3.1")
|
|
- **Docstrings**: Google style, specificare Direction e Rate
|
|
|
|
---
|
|
|
|
## 9. Limitazioni Note e Future Improvements
|
|
|
|
### 9.1 Limitazioni
|
|
|
|
1. **Non Real-Time**: Python + Tkinter non garantiscono latenza <10ms
|
|
- OK per monitoring, NON per control loop critico
|
|
|
|
2. **Single-Threaded GUI**: Tkinter bound a main thread
|
|
- Limite architetturale, non risolvibile
|
|
|
|
3. **Queue Overflow**: Messaggi possono essere scartati se >100Hz sostenuto
|
|
- Trade-off accettabile per evitare freeze
|
|
|
|
### 9.2 Possibili Miglioramenti
|
|
|
|
- **Database Logging**: Salvare messaggi su SQLite per analisi offline
|
|
- **Chart Plotting**: Grafici tempo reale con matplotlib
|
|
- **Config File**: YAML/JSON invece di environment variables
|
|
- **Multi-RT Support**: Gestire più RT simultanei
|
|
- **Playback Mode**: Replay da file logged
|
|
|
|
---
|
|
|
|
## 10. Riferimenti
|
|
|
|
- **MIL-STD-1553**: Interface Standard, Digital Time Division Command/Response Multiplex Data Bus
|
|
- **ICD GRIFO-F/TH**: Data Exchange Control Document (vedi `doc/ICD_DECD_FTH...pdf`)
|
|
- **Python Descriptors**: [PEP 252](https://peps.python.org/pep-0252/)
|
|
- **Tkinter Threading**: [Tkinter Thread Safety](https://tkdocs.com/tutorial/concepts.html#thread)
|
|
|
|
---
|
|
|
|
## 11. Troubleshooting - Problemi Risolti
|
|
|
|
### 11.1 Radar rimane in STANDBY
|
|
|
|
**Sintomo**: Il radar riceve messaggi ma rimane in modalità standby, non risponde ai comandi.
|
|
|
|
**Causa**: `standby_cmd` in A2 non impostato esplicitamente su `OFF`.
|
|
|
|
**Soluzione**:
|
|
```python
|
|
msg_a2.standby_cmd = StandbyStatus.OFF # CRITICAL!
|
|
```
|
|
|
|
### 11.2 Range Scale / Bar Scan non visualizzati
|
|
|
|
**Sintomo**: MFD del radar mostra "---" per Range Scale, Bars, Azimuth Scan.
|
|
|
|
**Causa**: Campi non inizializzati (valore 0 potrebbe non essere valido per alcuni enum).
|
|
|
|
**Soluzione**:
|
|
```python
|
|
msg_a2.range_scale = RangeScale.SCALE_20
|
|
msg_a2.bar_scan = BarScan.BAR_1
|
|
msg_a2.azimuth_scan = AzimuthScan.AZ_60
|
|
```
|
|
|
|
### 11.3 Nav Data non accettati dal radar
|
|
|
|
**Sintomo**: Radar ignora dati di navigazione, non visualizza posizione/altitudine.
|
|
|
|
**Causa**: Bit di validità non impostati correttamente (logica inversa: 0=VALID, 1=INVALID).
|
|
|
|
**Soluzione**:
|
|
```python
|
|
# Tutti i validity bits devono essere 0 per dati validi
|
|
msg_a4.nav_data_invalid = 0
|
|
msg_a4.attitude_data_invalid = 0
|
|
msg_a4.baro_inertial_alt_invalid = 0
|
|
# ... e fornire dati plausibili
|
|
msg_a4.baro_inertial_alt = 10000.0
|
|
msg_a4.tas = 300.0
|
|
```
|
|
|
|
### 11.4 GUI progressivamente lenta / freeze
|
|
|
|
**Sintomo**: Interfaccia diventa sempre meno reattiva fino a bloccarsi.
|
|
|
|
**Causa**: `root.after()` chiamato da thread esterni accumula eventi nella coda Tkinter.
|
|
|
|
**Soluzione**: Implementato pattern Queue + Rate Limiting (vedi sezione 5).
|
|
|
|
### 11.5 Messaggi inviati a rate sbagliato
|
|
|
|
**Sintomo**: Tutti i messaggi inviati a 1Hz invece dei rate ICD.
|
|
|
|
**Causa**: Scheduler originale con sleep fisso a 1 secondo.
|
|
|
|
**Soluzione**: Implementato tick-based scheduler a 200Hz (vedi sezione 4).
|
|
|
|
---
|
|
|
|
## Conclusione
|
|
|
|
Questo documento descrive le scelte architetturali e implementative di PyBusMonitor1553. Le decisioni principali (field descriptors, multi-rate scheduler, GUI disaccoppiamento) sono motivate da requisiti specifici:
|
|
|
|
- **Correttezza**: Aderenza al protocollo MIL-STD-1553 e ICD
|
|
- **Performance**: GUI reattiva anche sotto carico
|
|
- **Manutenibilità**: Codice dichiarativo e testabile
|
|
- **Estensibilità**: Facile aggiungere nuovi messaggi
|
|
|
|
Per domande o chiarimenti, consultare il codice commentato o contattare i maintainer del progetto.
|