# Capitolo 4: Sistema di Test - Workflow e Logica

[← Script Principale](03_Script_Principale_GRIFO_M_PBIT.md) | [Torna all'Indice](00_INDEX.md) | [Prossimo: Modalità Esecuzione →](05_Modalita_Esecuzione.md)

---

## 4.1 Ciclo di Test Completo

Ogni run di test segue un workflow standardizzato con 7 fasi principali. Questo approccio garantisce ripetibilità e tracciabilità completa.

### 4.1.1 Timeline Tipica di una Run

```
Time    Phase                  Activity                          Duration
═════════════════════════════════════════════════════════════════════════
00:00   Power Cycling          Power OFF radar                   ~5s
        └─ wait_before=1s      Stabilizzazione pre-spegnimento
        └─ wait_after=4s       Scarica condensatori
        
00:05   Power Cycling          Power ON radar                    ~2s
        └─ wait_after=1.5s     Inizializzazione radar
        
00:07   Radar Setup            Configura messaggi 1553           ~1s
        └─ A1/A2/A4/A5         Intensità, mode, nav, INU
        
00:08   PBIT Execution         Invia comando PBIT                ~15-20s
        └─ enter_pbit()        Attiva Built-In Test
        └─ wait_completion()   Polling bit_available flag
        
00:28   B6 Verification        Verifica 12 campi LRU status      ~1s
        └─ verify_b6_status()  Classifica PASS/FAIL/KNOWN
        
00:29   B8 Drill-Down          Drill-down 185 campi diagnostici  ~5-10s
        └─ drill_down_b8()     (solo se real failures)
        
00:39   Serial Check           Parsing buffer seriale            ~1s
        └─ extract_stats()     Conta %%E, %%F, RECYCLE
        
00:40   Target Test            Rilevamento target (opzionale)    ~15-20s
        └─ tgt_gen()           Monitoring B9 messages
        
01:00   Finalization           Aggregazione statistiche          ~1s
        └─ update_gui()        Aggiorna interfaccia
═════════════════════════════════════════════════════════════════════════
TOTAL:  ~60 secondi per run (senza target) / ~80s (con target)
```

### 4.1.2 Diagramma Stati Run

```
                    ┌──────────────┐
                    │  RUN START   │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │ POWER_OFF    │──┐ Failure: Abort Run
                    └──────┬───────┘  │
                           │          │
                    ┌──────▼───────┐  │
                    │ POWER_ON     │──┤
                    └──────┬───────┘  │
                           │          │
                    ┌──────▼───────┐  │
                    │ RADAR_SETUP  │──┤
                    └──────┬───────┘  │
                           │          │
                    ┌──────▼───────┐  │
                    │ PBIT_START   │──┤
                    └──────┬───────┘  │
                           │          │
              ┌────────────▼────────┐ │
              │ PBIT_WAIT_COMPLETE  │─┤
              └────────────┬────────┘ │
                           │          │
                    ┌──────▼───────┐  │
                    │ B6_CHECK     │  │
                    └──────┬───────┘  │
                           │          │
                  ┌────────▼────────┐ │
                  │ B8_DRILLDOWN?   │ │
                  │ (if failures)   │ │
                  └────────┬────────┘ │
                           │          │
                    ┌──────▼───────┐  │
                    │ SERIAL_CHECK │  │
                    └──────┬───────┘  │
                           │          │
                  ┌────────▼────────┐ │
                  │ TARGET_TEST?    │ │
                  │ (if enabled)    │ │
                  └────────┬────────┘ │
                           │          │
                    ┌──────▼───────┐  │
                    │  FINALIZE    │◄─┘
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │   RUN END    │
                    │ (success or  │
                    │   failure)   │
                    └──────────────┘
```

## 4.2 Scenari di Test

Il sistema supporta 6 scenari pre-configurati, ciascuno progettato per testare aspetti specifici del radar.

### 4.2.1 Scenario: Random Component Failures

**Scopo**: Simulare failure casuali per testare robustezza sistema

**Configurazione**:
```python
{
    'name': 'Random Component Failures',
    'description': 'Random selection of component failures (30% proc, 20% tx, 15% rx)',
    'expected_failures': [
        'processor_status',
        'transmitter_status', 
        'receiver_status'
    ],
    'failure_probability': {
        'processor_status': 0.30,    # 30% chance ogni run
        'transmitter_status': 0.20,  # 20% chance
        'receiver_status': 0.15      # 15% chance
    },
    'target_gen': None
}
```

**Behavior Simulato**:
- Ogni run, mock decide casualmente quali componenti falliscono
- Probabilità configurabili per failure realistici
- Usa per stress-testing handling failure multipli

**Risultati Attesi**: ~50-60% run FAIL (combinazione probabilità)

### 4.2.2 Scenario: Recycle Self-Healing

**Scopo**: Verifica capacità auto-recupero dopo processor recycle

**Configurazione**:
```python
{
    'name': 'RECYCLE Self-Healing (1 cycle)',
    'description': 'Processor fails, then auto-recovers after 1 RECYCLE event',
    'expected_failures': [
        'processor_status'  # Initial failure
    ],
    'serial_events': [
        {'type': 'recycle', 'iteration': 1}  # Recycle dopo 1 iterazione
    ],
    'auto_recovery': True,  # Failure scompare dopo recycle
    'target_gen': None
}
```

**Sequence**:
1. Run 1: Processor fails → Serial mostra "RECYCLE" event
2. Run 2+: Processor OK (auto-recovery completato)

**Risultati Attesi**: Run 1 FAIL, Run 2+ PASS

### 4.2.3 Scenario: Perfect Run

**Scopo**: Baseline - tutti i test passano

**Configurazione**:
```python
{
    'name': 'Perfect Run (No Failures)',
    'description': 'All tests pass - ideal scenario',
    'expected_failures': [],
    'failure_probability': {},
    'target_gen': None
}
```

**Risultati Attesi**: 100% run PASS

### 4.2.4 Scenario: Known Failures Only

**Scopo**: Simula limitazioni HW setup noto (es. antenna disconnessa)

**Configurazione**:
```python
{
    'name': 'Known Failures Only',
    'description': 'Only expected failures due to HW setup limitations',
    'expected_failures': [
        'antenna_drive_unit_status',
        'waveguide_pressurization_status'
    ],
    'failure_probability': {
        'antenna_drive_unit_status': 1.0,      # Sempre fail
        'waveguide_pressurization_status': 1.0  # Sempre fail
    },
    'target_gen': None
}
```

**Comportamento**: 
- Run risulta PASS (known failures ignorati)
- Non trigger B8 drill-down
- Utile per test su HW parzialmente configurato

### 4.2.5 Scenario: Target Generation (TgtGen)

**Scopo**: Verifica capacità rilevamento target + PBIT

**Configurazione**:
```python
{
    'name': 'Target Generation Test',
    'description': 'PBIT + Target detection at 2000m @ cycle 3',
    'expected_failures': [],
    'target_gen': {
        'distance': 2000,              # metri
        'appeared_after_cycles': 3,    # Visibile dopo 3 cicli B9
        'heading': 1060,               # ICD units
        'x_pos': 2500,
        'y_pos': 2500,
        'range_tolerance_low': -1000,  # Tolerance ±1000m
        'range_tolerance_high': 200
    }
}
```

**Sequence**:
1. PBIT execution (normale)
2. B6/B8 verification
3. **Target test attivato**:
   - Set A2 mode = OPERATE
   - Configura A4/A5 per target scenario
   - Monitor B9 per 20 secondi
   - Verifica range detection

**Risultati Attesi**: PBIT PASS + Target DETECTED

### 4.2.6 Scenario: Multi-Failure Cascade

**Scopo**: Testa worst-case con failure multipli simultanei

**Configurazione**:
```python
{
    'name': 'Multi-Failure Cascade',
    'description': 'Multiple failures across different subsystems',
    'expected_failures': [],  # Tutti unexpected
    'failure_probability': {
        'processor_1_status': 0.80,
        'processor_2_status': 0.80,
        'receiver_status': 0.70,
        'transmitter_status': 0.70,
        'exciter_status': 0.60
    },
    'target_gen': None
}
```

**Risultati Attesi**: ~95% run FAIL (stress test massimo)

## 4.3 Test B6 - Verifiche LRU Status

### 4.3.1 Messaggi B6 nel Protocollo 1553

Il messaggio **B6_MsgRdrSettingsAndParametersTellback** è il cuore della diagnostica BIT.

**Struttura B6**:
```
Message: B6_MsgRdrSettingsAndParametersTellback
RT: 1 (Radar)
Direction: RT → BC (Telemetry)
Frequency: ~10 Hz (100ms period)
Size: ~32 words (64 bytes)
```

**Campi Rilevanti**:
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `b6_t1_bit_available` | bool | BIT completato (true=done) |
| `b6_t1_rdr_fail_status` | bool | Overall radar failure |
| `b6_t1_processor_1_status` | bool | Processor 1 LRU status |
| `b6_t1_processor_2_status` | bool | Processor 2 LRU status |
| `b6_t1_receiver_status` | bool | Receiver LRU status |
| `b6_t1_exciter_status` | bool | Exciter LRU status |
| `b6_t1_transmitter_status` | bool | Transmitter LRU status |
| `b6_t1_antenna_drive_unit_status` | bool | ADU status |
| `b6_t1_power_supply_status` | bool | Power supply status |
| `b6_t1_auxilliary_processor_status` | bool | Aux processor status |
| `b6_t1_waveguide_pressurization_status` | bool | Pressurization status |
| `b6_t1_auxilliary_siu_status` | bool | Aux SIU status |
| `b6_t1_receiver_transmitter_status` | bool | Rx/Tx combined status |

**Semantica Valori**:
- `"false"` = LRU OK (no failure)
- `"true"` = LRU FAIL (failure detected)

### 4.3.2 Logica Verifica B6

**Implementazione**:
```python
def verify_b6_status(known_failures: List[str]) -> Dict:
    """
    Verifica tutti i 12 campi B6 LRU status.
    Classifica failure come PASS/FAIL/KNOWN.
    """
    B6_FIELDS = [
        "b6_t1_rdr_fail_status",
        "b6_t1_processor_1_status",
        "b6_t1_processor_2_status",
        "b6_t1_receiver_status",
        "b6_t1_exciter_status",
        "b6_t1_transmitter_status",
        "b6_t1_antenna_drive_unit_status",
        "b6_t1_power_supply_status",
        "b6_t1_auxilliary_processor_status",
        "b6_t1_waveguide_pressurization_status",
        "b6_t1_auxilliary_siu_status",
        "b6_t1_receiver_transmitter_status"
    ]
    
    b6_pass = 0
    b6_fail = 0
    b6_known_fail = 0
    failures = []
    
    for field in B6_FIELDS:
        # Check: expected value = "false" (no failure)
        ret, err = check(theGrifo1553, "false", 
                        "B6_MsgRdrSettingsAndParametersTellback", field)
        
        if ret:  # Field == "false" → OK
            b6_pass += 1
        else:    # Field == "true" → FAIL
            # Leggi valore attuale per logging
            actual_value = getValue(theGrifo1553,
                                   "B6_MsgRdrSettingsAndParametersTellback", field)
            
            # Classifica: Known o Real failure?
            if field in known_failures:
                b6_known_fail += 1
                logging.info(f"B6 Known failure: {field} = {actual_value}")
            else:
                b6_fail += 1
                failures.append((field, actual_value))
                logging.error(f"B6 REAL FAILURE: {field} = {actual_value}")
    
    return {
        'b6_pass': b6_pass,
        'b6_fail': b6_fail,
        'b6_known_fail': b6_known_fail,
        'failures': failures
    }
```

**Decision Logic**:
```
FOR each B6 field:
    IF field_value == "false":
        → PASS (incrementa b6_pass)
    ELSE IF field_value == "true":
        IF field in known_failures_list:
            → KNOWN (incrementa b6_known_fail)
        ELSE:
            → FAIL (incrementa b6_fail, append to failures)
```

**Criterio Run Success**:
```python
run_success = (b6_fail == 0)  # PASS solo se NO real failures
# Known failures NON influenzano run_success
```

### 4.3.3 Esempio Output B6

**Scenario: 2 real failures, 1 known**

```
B6 Verification Results:
  Pass:         9 fields
  Fail:         2 fields (REAL)
    - b6_t1_processor_1_status = true
    - b6_t1_transmitter_status = true
  Known Fail:   1 field
    - b6_t1_antenna_drive_unit_status = true (EXPECTED)

Run Result: FAIL (due to 2 real failures)
```

## 4.4 Test B8 - Drill-Down Diagnostico

### 4.4.1 Quando Eseguire B8 Drill-Down

Il comportamento è stato aggiornato per tenere conto delle `KNOWN_FAILURES`.
Il drill-down B8 viene eseguito quando sono presenti real failures (non-known)
o quando è forzato (opzione `FORCE_B8_DRILL_DOWN`). Se tutti i fallimenti
osservati sono conosciuti, il drill-down può essere saltato per risparmiare tempo.

**Trigger Condition (semplificata)**:
```python
# Esegui drill-down se ci sono real failures oppure se forzato
if b6_fail > 0 and b6_real_fail > 0:
    drill_down_b8()
elif FORCE_B8_DRILL_DOWN:
    drill_down_b8()
else:
    # Tutti i fallimenti (se presenti) sono classificati come KNOWN -> skip
    logging.info("B8 drill-down skipped (no real failures)")
```

Rationale: B8 contiene 185 campi diagnostici. Eseguire sempre sarebbe dispendioso
(5-10s). Il criterio adottato mantiene il drill-down per le situazioni che
richiedono indagine (real failures) e lo salta quando si tratta solo di
known failures attese.

### 4.4.2 Struttura Messaggio B8

**B8_MsgBitReport**:
```
Message: B8_MsgBitReport
RT: 1 (Radar)
Direction: RT → BC
Frequency: On-demand (solo se BIT eseguito)
Size: ~185 fields (diagnostic details)
```

**Categorie Campi B8**:
| Categoria | # Campi | Esempio |
|-----------|---------|---------|
| Processor Tests | 45 | `b8_processor_1_rom_test`, `b8_processor_2_ram_test` |
| Receiver Tests | 35 | `b8_receiver_sensitivity_test`, `b8_receiver_noise_figure` |
| Transmitter Tests | 30 | `b8_transmit_power_test`, `b8_modulator_test` |
| Exciter Tests | 25 | `b8_exciter_frequency_test`, `b8_exciter_stability` |
| Antenna Tests | 20 | `b8_antenna_rotation_test`, `b8_antenna_position` |
| Power Supply Tests | 15 | `b8_psu_voltage_28v`, `b8_psu_current_limit` |
| Misc Tests | 15 | `b8_cooling_fan_test`, `b8_temperature_sensor` |

**Semantica**:
- `"false"` = Test PASS
- `"true"` = Test FAIL

### 4.4.3 Implementazione Drill-Down B8

```python
def drill_down_b8() -> Dict:
    """
    Drill-down dettagliato B8: verifica 185 campi diagnostici.
    Eseguito solo quando B6 mostra real failures.
    """
    B8_FIELDS = [
        "b8_processor_1_rom_test",
        "b8_processor_1_ram_test",
        "b8_processor_2_rom_test",
        # ... (185 total fields)
    ]
    
    b8_pass = 0
    b8_fail = 0
    b8_known_fail = 0
    b8_checked = len(B8_FIELDS)
    b8_failures = []
    b8_known_failures = []
    
    logging.info(f"Starting B8 drill-down: {b8_checked} fields to check")
    start_time = time.time()
    
    for field in B8_FIELDS:
        try:
            ret, err = check(theGrifo1553, "false",
                           "B8_MsgBitReport", field)
            
            if ret:
                b8_pass += 1
            else:
                # Leggi valore per logging
                value = getValue(theGrifo1553, "B8_MsgBitReport", field)
                # Classifica known vs real failure usando is_known_failure()
                if is_known_failure(field):
                    b8_known_fail += 1
                    b8_known_failures.append((field, value))
                    logging.info(f"Known failure (B8 ignored): {field} = {value}")
                else:
                    b8_fail += 1
                    b8_failures.append((field, value))
                    logging.warning(f"B8 REAL FAIL: {field} = {value}")
        
        except Exception as e:
            logging.error(f"B8 check failed for {field}: {e}")
            # Continua con campo successivo
    
    elapsed = time.time() - start_time
    logging.info(f"B8 drill-down completed in {elapsed:.2f}s")
    logging.info(f"B8 Results: {b8_pass} PASS, {b8_fail} FAIL, {b8_known_fail} KNOWN")
    
    return {
        'b8_pass': b8_pass,
        'b8_fail': b8_fail,
        'b8_known_fail': b8_known_fail,
        'b8_checked': b8_checked,
        'b8_failures': b8_failures,
        'b8_known_failures': b8_known_failures,
        'b8_time': elapsed
    }
```

**Performance**: ~5-10 secondi per 185 field checks (depends on 1553 bus latency)

### 4.4.4 Correlazione B6 → B8

Quando B6 mostra failure LRU, B8 drill-down identifica il test specifico fallito:

**Esempio Correlazione**:
```
B6: b6_t1_processor_1_status = true (FAIL)
  ↓ B8 Drill-Down
B8: b8_processor_1_ram_test = true (FAIL)
    b8_processor_1_rom_test = false (PASS)
    b8_processor_1_watchdog = false (PASS)
    ...
  → Root Cause: Processor 1 RAM test failed
```

## 4.5 Test Seriale - Monitoring Messaggi

### 4.5.1 Porta Seriale e Protocollo

**Configurazione**:
```python
serial_config = {
    'port': 'COM4',         # Windows
    'baudrate': 115200,
    'bytesize': 8,
    'parity': 'N',
    'stopbits': 1,
    'timeout': 1.0          # Read timeout
}
```

**Formato Messaggi**:
```
[HH:MM:SS.mmm] <message_text>
```

### 4.5.2 Pattern Messaggi Riconosciuti

| Pattern | Significato | Contatore | Severità |
|---------|-------------|-----------|----------|
| `%%E` | Error message | `serial_errors` | WARNING |
| `%%F` | Fatal error | `serial_fatal` | ERROR |
| `RECYCLE` | Processor recycle event | `serial_recycles` | CRITICAL |
| `PBIT COMPLETED` | PBIT done | N/A | INFO |
| `BIT AVAILABLE` | BIT results ready | N/A | INFO |

**Esempi Messaggi Reali**:
```
[15:24:35.123] %%E Memory allocation failed in module RX_PROC
[15:24:35.456] %%F Critical fault detected - initiating RECYCLE
[15:24:36.789] RECYCLE event - processor restarting
[15:24:50.012] PBIT COMPLETED in 15.2 seconds
[15:24:50.123] BIT AVAILABLE - results on B6/B8
```

### 4.5.3 Parser Implementazione

```python
class SerialMessageParser:
    def __init__(self):
        self.statistics = {
            'total_messages': 0,
            'error_messages': 0,
            'fatal_messages': 0,
            'recycle_events': 0
        }
        self.message_buffer = []
    
    def parse_message(self, line: str) -> Dict[str, Any]:
        """Analizza singola riga seriale"""
        self.statistics['total_messages'] += 1
        
        # Extract timestamp
        timestamp_match = re.match(r'\[(\d{2}:\d{2}:\d{2}\.\d{3})\]', line)
        timestamp = timestamp_match.group(1) if timestamp_match else None
        
        # Remove timestamp from message
        message_text = re.sub(r'^\[\d{2}:\d{2}:\d{2}\.\d{3}\]\s*', '', line)
        
        # Detect pattern
        severity = 'INFO'
        if '%%E' in message_text:
            self.statistics['error_messages'] += 1
            severity = 'WARNING'
        elif '%%F' in message_text:
            self.statistics['fatal_messages'] += 1
            severity = 'ERROR'
        elif 'RECYCLE' in message_text:
            self.statistics['recycle_events'] += 1
            severity = 'CRITICAL'
        
        msg_info = {
            'timestamp': timestamp,
            'text': message_text,
            'severity': severity
        }
        
        self.message_buffer.append(msg_info)
        return msg_info
    
    def extract_statistics(self) -> Dict[str, int]:
        """Ritorna statistiche aggregate"""
        return self.statistics.copy()
```

### 4.5.4 Integrazione nel Test Flow

```python
# Durante test run
terminal = SerialTerminal(port='COM4', baudrate=115200)
terminal.connect()
parser = SerialMessageParser()

# In background, leggi messaggi seriali
while test_running:
    try:
        line = terminal.readline(timeout=1.0)
        if line:
            msg_info = parser.parse_message(line)
            logging.info(f"Serial [{msg_info['severity']}]: {msg_info['text']}")
    except Exception as e:
        logging.error(f"Serial read error: {e}")

# A fine run
serial_stats = parser.extract_statistics()
run_stats.update({
    'serial_errors': serial_stats['error_messages'],
    'serial_fatal': serial_stats['fatal_messages'],
    'serial_recycles': serial_stats['recycle_events']
})
```

## 4.6 Test Target Generation (TgtGen)

### 4.6.1 Workflow Target Detection

**Precondizioni**:
- PBIT completato con successo
- B6/B8 verifiche OK
- Scenario config contiene `target_gen` section

**Sequence**:
```
1. Configure A2: Set mode = OPERATE (exit BIT, enter operational mode)
2. Configure A4: Inject target parameters
   - a4_heading: Target bearing (ICD units)
   - a4_x_pos, a4_y_pos: Platform position
3. Configure A5: INU mode word (valid data)
4. Monitor B9: Poll B9_MsgRdrTrackAndTargetsData for target reports
5. Verify Range: Check if detected range within tolerance
6. Count Hits: Require at least N successful detections
7. Timeout: Max 20 seconds for target detection
```

### 4.6.2 Messaggi 1553 per Target

**A4_MsgNavDataAndCursor** (BC → RT):
```python
# Platform navigation data
setValue(theGrifo1553, 1060, "A4_MsgNavDataAndCursor", "a4_heading")  # degrees * 10
setValue(theGrifo1553, 2500, "A4_MsgNavDataAndCursor", "a4_x_pos")    # meters
setValue(theGrifo1553, 2500, "A4_MsgNavDataAndCursor", "a4_y_pos")    # meters
```

**A5_MsgInuHighSpeed** (BC → RT):
```python
# INU (Inertial Navigation Unit) mode word
setValue(theGrifo1553, 0xAA54, "A5_MsgInuHighSpeed", "a5_inu_mode_word")
```

**B9_MsgRdrTrackAndTargetsData** (RT → BC):
```python
# Target report from radar
target_range = getValue(theGrifo1553, "B9_MsgRdrTrackAndTargetsData", "b9_t1_rng")
target_azimuth = getValue(theGrifo1553, "B9_MsgRdrTrackAndTargetsData", "b9_t1_az")
```

### 4.6.3 Implementazione Target Test

```python
def tgt_gen(scenario_config: Dict) -> Dict:
    """
    Execute target detection test.
    """
    tgt_cfg = scenario_config.get('target_gen', {})
    if not tgt_cfg:
        return {'target_detected': None}
    
    # Extract configuration
    expected_distance = tgt_cfg['distance']  # meters
    appeared_after_cycles = tgt_cfg['appeared_after_cycles']
    heading = tgt_cfg['heading']
    x_pos = tgt_cfg['x_pos']
    y_pos = tgt_cfg['y_pos']
    
    logging.info(f"Starting target detection test: {expected_distance}m @ cycle {appeared_after_cycles}")
    
    # Set OPERATE mode
    setValue(theGrifo1553, 2, "A2_MsgRdrOperationCommand", "a2_radarMode", commitChanges=True)
    time.sleep(0.5)
    
    # Configure navigation
    setValue(theGrifo1553, heading, "A4_MsgNavDataAndCursor", "a4_heading", commitChanges=False)
    setValue(theGrifo1553, x_pos, "A4_MsgNavDataAndCursor", "a4_x_pos", commitChanges=False)
    setValue(theGrifo1553, y_pos, "A4_MsgNavDataAndCursor", "a4_y_pos", commitChanges=True)
    
    # Configure INU
    setValue(theGrifo1553, 0xAA54, "A5_MsgInuHighSpeed", "a5_inu_mode_word", commitChanges=True)
    time.sleep(1.0)
    
    # Monitor B9 for target
    target_found = False
    found_iteration = None
    found_range = None
    
    timeout = TARGET_DETECTION_TIMEOUT_SEC
    start_time = time.time()
    iteration = 0
    hit_count = 0
    
    while (time.time() - start_time) < timeout:
        iteration += 1
        
        try:
            # Read target range from B9
            range_value = getValue(theGrifo1553, "B9_MsgRdrTrackAndTargetsData", "b9_t1_rng")
            
            # Verify range within tolerance
            range_min = expected_distance + TARGET_RANGE_TOLERANCE_LOW
            range_max = expected_distance + TARGET_RANGE_TOLERANCE_HIGH
            
            if range_min <= range_value <= range_max:
                hit_count += 1
                logging.info(f"Target HIT #{hit_count}: range={range_value}m @ iteration {iteration}")
                
                if hit_count >= TARGET_HIT_THRESHOLD:
                    target_found = True
                    found_iteration = iteration
                    found_range = range_value
                    logging.info(f"Target DETECTED: {found_range}m @ cycle {found_iteration}")
                    break
        
        except Exception as e:
            logging.warning(f"B9 read error iteration {iteration}: {e}")
        
        time.sleep(0.1)  # Poll every 100ms
    
    elapsed = time.time() - start_time
    
    if not target_found:
        logging.warning(f"Target NOT detected after {elapsed:.1f}s ({iteration} cycles)")
    
    return {
        'target_detected': target_found,
        'target_range': found_range if target_found else None,
        'target_iterations': found_iteration if target_found else iteration,
        'target_test_time': elapsed
    }
```

### 4.6.4 Criteri Successo Target

```python
# Target considerato DETECTED se:
SUCCESS = (hit_count >= TARGET_HIT_THRESHOLD) AND \
          (range_min <= detected_range <= range_max) AND \
          (elapsed_time < TARGET_DETECTION_TIMEOUT_SEC)

# Parametri tipici:
TARGET_HIT_THRESHOLD = 2          # Almeno 2 detection consecutive
TARGET_RANGE_TOLERANCE_LOW = -1000  # ±1000m tolerance
TARGET_RANGE_TOLERANCE_HIGH = 200
TARGET_DETECTION_TIMEOUT_SEC = 20.0
```

---

[← Script Principale](03_Script_Principale_GRIFO_M_PBIT.md) | [Torna all'Indice](00_INDEX.md) | [Prossimo: Modalità Esecuzione →](05_Modalita_Esecuzione.md)
