# GRIFO Test Environment - Technical Analysis

**Document Version:** 1.0  
**Date:** 2026-01-29  
**Purpose:** Technical documentation of the GRIFO automatic test environment architecture

---

## Table of Contents

1. [Environment Overview](#environment-overview)
2. [Directory Structure](#directory-structure)
3. [Core Components](#core-components)
4. [1553 Interface Architecture](#1553-interface-architecture)
5. [Serial Communication](#serial-communication)
6. [Power Control](#power-control)
7. [Test Framework](#test-framework)
8. [Python Environment](#python-environment)
9. [Message Protocol](#message-protocol)
10. [Simulation Mode](#simulation-mode)

---

## Environment Overview

The GRIFO test environment is a Python-based automated test system for the GRIFO-F/TH radar system. It provides:

- **1553 bus interface** for radar communication (via native C++ library)
- **Serial terminal interface** for radar diagnostics monitoring
- **Power control** via BrainBox interface
- **PDF report generation** for test results
- **Comprehensive logging** and data recording

### Key Technologies

- **Python 3.x** (local installation in environment)
- **SWIG bindings** for native C++ 1553 library
- **Serial communication** (pyserial)
- **PDF generation** (fpdf2)
- **Custom test framework** (leo_grifo_* modules)

---

## Directory Structure

```
GrifoAutomaticTestEnv/
│
├── PlatformSimulator/                 # Native 1553 interface components
│   ├── bin/
│   │   ├── interpreter.py            # SWIG-generated Python wrapper
│   │   ├── _interpreter.pyd          # Native C++ DLL (1553 hardware driver)
│   │   └── help/                     # Documentation files (.chm)
│   └── (additional platform files)
│
├── TestEnvironment/
│   ├── env/                          # Python libraries and utilities
│   │   ├── leo_grifo_1553.py        # 1553 interface wrapper
│   │   ├── leo_grifo_terminal.py    # Serial terminal handler
│   │   ├── leo_grifo_io_box.py      # Power control (BrainBox)
│   │   ├── leo_grifo_common.py      # Common utilities
│   │   ├── leo_grifo_core.py        # Core framework (recorder)
│   │   ├── leo_grifo_test_report.py # PDF report generation
│   │   ├── test_common_function.py  # Test helper functions
│   │   ├── logger.py                # Logging setup
│   │   ├── site-packages/           # Local Python packages
│   │   │   ├── defusedxml/
│   │   │   ├── fpdf/
│   │   │   ├── PIL/
│   │   │   ├── pyvisa/
│   │   │   ├── serial/
│   │   │   └── ...
│   │   └── __pycache__/
│   │
│   ├── scripts/                      # Test scripts
│   │   ├── __init__.py              # Environment setup (sys.path config)
│   │   ├── GRIFO_M_PBIT.py          # Main PBIT test script
│   │   ├── GRIFO_M_PBIT_mock.py     # Simulation mode mock (NEW)
│   │   └── (other test scripts)
│   │
│   ├── json/                         # Configuration files
│   │   └── (test configurations)
│   │
│   ├── LOG/                          # Test execution logs
│   └── pdf_reports/                  # Generated test reports
│
├── Documents/                         # Project documentation
└── run_batch.bat                      # Batch execution scripts
```

---

## Core Components

### 1. `__init__.py` - Environment Bootstrap

**Location:** `TestEnvironment/scripts/__init__.py`

**Purpose:** Configure Python import paths for test scripts

```python
import os, sys
LIB_DIR = os.path.join(os.path.dirname(__file__), '..', 'env')
sys.path.append(LIB_DIR)
sys.path.append(os.path.join(LIB_DIR, 'site-packages'))
```

**Key Function:**
- Adds `env/` to Python path (for leo_grifo_* modules)
- Adds `env/site-packages/` to Python path (for dependencies)
- Enables relative imports from test scripts

---

## 1553 Interface Architecture

### Native Layer: `interpreter` Module

**Location:** `PlatformSimulator/bin/interpreter.py` + `_interpreter.pyd`

**Type:** SWIG-generated Python bindings for C++ library

**Key Classes:**
- `PyInterfaceManager` - Main interface factory
- `Grifo 1553 Interface` - Specific 1553 hardware interface

**Functionality:**
- Low-level 1553 bus communication
- Message transmission/reception
- Field access and manipulation
- Hardware-specific drivers

### Python Wrapper: `leo_grifo_1553.py`

**Location:** `TestEnvironment/env/leo_grifo_1553.py`

**Class:** `GrifoInstrumentInterface(TestCommonInterface, ABC)`

**Key Methods:**

```python
def __init__(self, _timeout=0.5):
    """Initialize interface and get hardware reference"""
    mgr = interpreter.PyInterfaceManager()
    index = mgr.indexOf('Grifo 1553 Interface')
    self.grifo_1553 = mgr.getInterface(index)
    self.run(True)  # Start message reception

def check(self, expected_result, *fields, **kwargs) -> (bool, Any, AnyStr):
    """
    Verify message field value with optional timing and retry logic.
    
    Args:
        expected_result: Expected value or range (tuple for range)
        *fields: (message_name, field_name)
        **kwargs: 
            - timeout: Max wait time for message reception
            - step: Polling interval for retry logic
    
    Returns:
        (success: bool, value: Any, error: str)
    """

def set(self, value, *fields, **kwargs) -> (bool, AnyStr):
    """
    Assign value to TX message field and optionally send.
    
    Args:
        value: Field value (None = send last committed message)
        *fields: (message_name, field_name)
        **kwargs:
            - commitChanges: Send message after assignment
            - errorInject: Inject CRC error for testing
    """

def get(self, *fields, **kwargs) -> (Any, AnyStr):
    """
    Read message field value.
    
    Args:
        *fields: (message_name, field_name)
        
    Returns:
        (value: Any, error: str)
    """

def getInterface(self):
    """Return raw 1553 interface object for advanced operations"""
    return self.grifo_1553

def run(self, enable: bool):
    """Start/stop message reception"""
    if enable:
        self.grifo_1553.start()
    else:
        self.grifo_1553.stop()
```

**Global Singleton:**
```python
theGrifo1553 = GrifoInstrumentInterface(0.2)
```

**Usage Pattern in Tests:**
```python
from leo_grifo_1553 import theGrifo1553

# Get interface object
interface = theGrifo1553.getInterface()

# Read message counter
count = interface.getSingleMessageReceivedSz("B6_MsgRdrSettingsAndParametersTellback")

# Read field value
value = interface.getMessageFieldValue("B6_MsgRdrSettingsAndParametersTellback", 
                                       "radar_health_status_...")

# Check field with automatic retry logic
success, value, error = theGrifo1553.check(
    "false",  # Expected value
    "B6_MsgRdrSettingsAndParametersTellback",
    "radar_health_status_...",
    timeout=5.0,  # Wait up to 5s for message
    step=0.1      # Poll every 100ms
)
```

---

## Serial Communication

### Base Class: `Terminal2Serial`

**Location:** `TestEnvironment/env/leo_terminal_serial.py`

**Purpose:** Generic serial communication wrapper using pyserial

**Features:**
- Configurable port, baud rate, stop bits, parity
- Line-feed mode for text-based protocols
- Callback-based message reception

### GRIFO Implementation: `GrifoSerialTerminal`

**Location:** `TestEnvironment/env/leo_grifo_terminal.py`

**Class:** `GrifoSerialTerminal(Terminal2Serial)`

**Configuration:** Loaded from JSON config
```python
{
    "serial_terminal": {
        "port": "COM2",
        "speed": "9600",
        "stop_bit": "1",
        "bytesize": 8,
        "parity": "N",
        "mode": "EXPECT_LF"
    }
}
```

**Message Pattern Detection:**
- `%%E` - Error messages (logged as critical)
- `%%F` - Fatal messages (logged as critical)
- `RECYCLE` - System restart keyword (case-insensitive)

**Statistics Tracking:**
```python
self._serial_stats = {
    'total_messages': 0,        # Total messages received
    'error_messages': 0,        # %%E count
    'fatal_messages': 0,        # %%F count
    'recycle_count': 0,         # RECYCLE keyword occurrences
    'error_details': [],        # [(timestamp, message), ...]
    'fatal_details': [],        # [(timestamp, message), ...]
    'recycle_details': [],      # [(timestamp, message), ...]
}
```

**API Methods:**
```python
def connect(self):
    """Connect to serial port"""

def disconnect(self):
    """Disconnect from serial port"""

def get_serial_statistics(self) -> dict:
    """Get current statistics snapshot"""
    return dict copy of _serial_stats

def reset_serial_statistics(self):
    """Reset statistics for new test run"""
```

**Usage Pattern:**
```python
import leo_grifo_terminal

terminal = leo_grifo_terminal.GrifoSerialTerminal()
terminal.connect()

# At test run start
terminal.reset_serial_statistics()

# During test execution
# (messages received automatically in background)

# At test run end
stats = terminal.get_serial_statistics()
print(f"Errors: {stats['error_messages']}")
print(f"Fatal: {stats['fatal_messages']}")
print(f"Recycles: {stats['recycle_count']}")

terminal.disconnect()
```

---

## Power Control

### BrainBox Interface: `leo_grifo_io_box.py`

**Location:** `TestEnvironment/env/leo_grifo_io_box.py`

**Purpose:** Control radar power via external control box

**Global Singleton:**
```python
theBrainBox = BrainBoxInterface()
```

**Usage (via helper functions):**
```python
from test_common_function import power_grifo_on, power_grifo_off

# Power on radar with 3s wait
power_grifo_on(wait_after=3)

# Power off radar
power_grifo_off(wait_after=0)
```

**Implementation:**
```python
def power_grifo_on(wait_after=0):
    setValue(theBrainBox, True, 'MAIN_POWER')
    ret, err = check(theBrainBox, 1, 'MAIN_POWER')
    time.sleep(wait_after)
    return ret

def power_grifo_off(wait_after=0):
    setValue(theBrainBox, False, 'MAIN_POWER')
    ret, err = check(theBrainBox, 0, 'MAIN_POWER', timeout=0.1)
    time.sleep(wait_after)
    return ret
```

---

## Test Framework

### Test Report: `leo_grifo_test_report.py`

**Class:** `testReport`

**Methods:**
```python
def __init__(self, test_name):
    """Initialize report with test name"""

def open_session(self, session_name):
    """Start new test session section"""

def close_session(self):
    """End current session"""

def add_comment(self, comment):
    """Add text to report"""

def generate_pdf(self):
    """Generate final PDF report"""
```

**Usage Pattern:**
```python
from leo_grifo_test_report import testReport

report = testReport(sys.argv[0])

report.open_session('Test Initialization')
report.add_comment('Starting test...')
# ... test operations ...
report.close_session()

report.generate_pdf()
```

### Common Functions: `test_common_function.py`

**Key Utilities:**
```python
def check(interface, expected, *fields, **kwargs):
    """Universal check function for any interface"""

def setValue(interface, value, *fields, **kwargs):
    """Universal set function for any interface"""

def get_test_name(file):
    """Extract test name from script file path"""

def startTest() -> datetime:
    """Log test start time"""

def stopTest() -> datetime:
    """Log test stop time"""
```

### Core Recorder: `leo_grifo_core.py`

**Global Object:**
```python
theRecorder = DataRecorder()
```

**Purpose:** Record test steps for later analysis/playback

**Methods:**
```python
def add_step(self, data, success, description, error):
    """Record test step with outcome"""

def logStart(self, verbosity, log_dir):
    """Start recording session"""

def logStop(self):
    """Stop recording session"""
```

---

## Python Environment

### Local Packages (site-packages)

**Location:** `TestEnvironment/env/site-packages/`

**Installed Packages:**
- `defusedxml` - Secure XML parsing
- `fpdf2` - PDF generation
- `Pillow (PIL)` - Image processing
- `pyvisa` - Instrument control (if needed)
- `pyserial` - Serial communication

**Python Version:** Python 3.x (exact version determined by environment)

**Import Resolution Order:**
1. Test script directory
2. `env/` directory (leo_grifo_* modules)
3. `env/site-packages/` (dependencies)
4. System Python paths

---

## Message Protocol

### 1553 Message Structure

**Message Types Used in Tests:**

#### B6: `B6_MsgRdrSettingsAndParametersTellback`
- **Direction:** RX (from radar to test system)
- **Purpose:** Radar settings confirmation + LRU status
- **Frequency:** 10 Hz (100ms period)
- **Key Fields:**
  - `bit_report_available` - BIT completion flag
  - `radar_fail_status` - Overall radar health (enum)
  - `array_status`, `pedestal_status`, etc. - LRU status (inverse logic)
  - `pressurization_status`, temperature alarms

**LRU Status Fields (12 total):**
```python
# Inverse logic: false = OK, true = FAIL
"array_status"
"pedestal_status"
"processor_status"
"receiver_status"
"rx_front_end_status"
"servoloop_status"
"trasmitter_status"
"pressurization_status"
"processor_over_temperature_alarm"
"servoloop_over_temperature_alarm"
"trasmitter_over_temperature_alarm"

# Enum: RDR_OK / RDR_FAIL
"radar_fail_status"
```

#### B8: `B8_MsgBitReport`
- **Direction:** RX (from radar to test system)
- **Purpose:** Detailed BIT diagnostic results
- **Frequency:** 10 Hz (100ms period)
- **Key Field Categories:**
  - Degradation conditions (12 fields)
  - SRU failure locations (43 fields across 6 subsystems)
  - Test results (118 fields across 10 test types)

**Field Categories:**
1. **Degradation Conditions** (12 fields)
   - System-level failures (BCN, groups, total radar fail)
   
2. **SRU Failure Locations** (43 fields)
   - Pedestal (5 SRUs)
   - Processor (14 SRUs)
   - Receiver (7 SRUs)
   - RX Frontend (6 SRUs)
   - Servoloop (3 SRUs)
   - Transmitter (8 SRUs)

3. **Test Results** (118 fields)
   - AGC tests (11)
   - Data Processor tests (14)
   - Integrated System tests (15)
   - Post Processor tests (8)
   - Power Supply tests (2)
   - Receiver/RX Frontend tests (7)
   - RX Module tests (6)
   - Servoloop tests (15)
   - Signal Processor tests (14)
   - Transmitter tests (26)

#### B9: Message (target data)
- **Direction:** RX (from radar)
- **Purpose:** Target track data
- **Frequency:** 50 Hz (20ms period)

### Message Field Naming Convention

**Pattern:** `{subsystem}_{structure}_{field_name}`

**Example:**
```
radar_health_status_and_bit_report_valid_RdrHealthStatusAndBitReport_processor_status
│                                                                      │
└─ Subsystem/Group                                                    └─ Field name
```

---

## Simulation Mode

### Overview

Simulation mode allows test execution without physical hardware using mock objects.

**Activation:**
```bash
python GRIFO_M_PBIT.py --simulate
```

### Architecture

**Mock Module:** `GRIFO_M_PBIT_mock.py`

**Key Components:**

1. **`MockGrifo1553Interface`**
   - Simulates 1553 message reception
   - Configurable BIT timing (15-25s default)
   - Scenario-based field values (normal/pedestal_fail/random_failures)

2. **`MockSerialTerminal`**
   - Simulates serial message reception
   - Generates %%E, %%F, RECYCLE messages
   - Compatible API with real terminal

3. **`MockBrainBox`**
   - Simulates power control (no-op)
   - Logs power state changes

4. **`setup_simulation()`**
   - Monkey-patches global singletons
   - Replaces `theGrifo1553` with mock
   - Replaces `theBrainBox` with mock

### Configuration Options

```python
# In GRIFO_M_PBIT_mock.py

# BIT completion timing
PBIT_TIME_MIN = 15.0
PBIT_TIME_MAX = 25.0

# Test scenario
SIMULATION_SCENARIO = 'normal'  # or 'pedestal_fail' or 'random_failures'

# Serial message generation
SIMULATE_SERIAL_ERRORS = True   # Generate %%E messages
SIMULATE_SERIAL_FATAL = False   # Generate %%F messages
SIMULATE_RECYCLE_EVENTS = True  # Generate RECYCLE at power-on
```

### Usage in Tests

**Minimal Changes Required:**
```python
def test_proc():
    # Simulation mode detection
    if '--simulate' in sys.argv:
        from GRIFO_M_PBIT_mock import setup_simulation, create_mock_terminal
        setup_simulation()
        use_mock_terminal = True
    else:
        use_mock_terminal = False
    
    # ... test initialization ...
    
    # Terminal creation (conditional)
    if use_mock_terminal:
        terminal = create_mock_terminal()
    else:
        terminal = leo_grifo_terminal.GrifoSerialTerminal()
    
    # Rest of test remains UNCHANGED
```

**Key Benefits:**
- ✅ Zero impact on production test code
- ✅ No modification to environment Python
- ✅ No dependency changes
- ✅ Transparent operation (same API)
- ✅ Reproducible test scenarios
- ✅ Faster test development/debugging

---

## Known Issues and Limitations

### 1. Inverse Logic Fields

Many status fields use **inverse logic**:
- `false` = OK/PASS
- `true` = FAIL

**Affected Fields:**
- All LRU status fields in B6
- Most test result fields in B8

**Reason:** Hardware convention for active-high failure flags

### 2. Known Hardware Setup Limitations

Some tests may consistently fail in certain setups:
- **Pedestal status**: Often fails when physical pedestal unit not connected
- **Pressurization status**: Requires sealed system

**Solution:** Use `KNOWN_FAILURES` configuration list to track expected failures

### 3. Native Library Dependencies

The `interpreter` module requires:
- `_interpreter.pyd` native DLL
- Compatible hardware drivers
- Proper 1553 bus interface card

**Not portable** across different systems without hardware.

### 4. Timing Considerations

- **BIT Completion:** Typically 15-30s, but can vary
- **Message Periods:** Fixed by radar firmware
  - B6: 100ms (10 Hz)
  - B7: 40ms (25 Hz)
  - B8: 100ms (10 Hz)
  - B9: 20ms (50 Hz)

---

## Test Execution Flow

### Standard Execution (with Hardware)

```
1. Initialize environment
   ├─ Load Python modules
   ├─ Connect to 1553 interface
   ├─ Connect to serial terminal
   └─ Initialize report

2. Test preparation
   ├─ Power off radar
   └─ Wait stabilization

3. For each test repetition:
   ├─ Power on radar
   ├─ Wait for BIT completion (180s timeout)
   ├─ Verify B6 LRU status (12 fields)
   ├─ If failures: drill-down B8 diagnostics (185 fields)
   ├─ Collect serial statistics
   ├─ Generate per-run report
   └─ Power off radar

4. Generate final statistics report
   ├─ Aggregate all runs
   ├─ Timing analysis
   ├─ Failure analysis
   └─ Test verdict

5. Cleanup
   ├─ Disconnect serial
   ├─ Generate PDF
   └─ Exit
```

### Simulated Execution (without Hardware)

Same flow, but with mock objects:
- No actual 1553 communication
- No actual serial port access
- No actual power control
- Simulated timing and responses

**Execution Time:** ~5 minutes for 10 runs (vs 30+ minutes real)

---

## Troubleshooting

### Common Issues

1. **Import Error: `interpreter` module not found**
   - Ensure `PlatformSimulator/bin` is accessible
   - Check Python path configuration
   - Verify native DLL exists

2. **Serial Port Error: Cannot open COM port**
   - Check port configuration in JSON
   - Verify port permissions
   - Ensure port not in use by another application

3. **BIT Never Completes (timeout)**
   - Check 1553 message reception
   - Verify radar power state
   - Check `bit_report_available` field polling

4. **Field Value Always `None`**
   - Verify message name spelling
   - Check field name (case-sensitive)
   - Ensure message being received (check counter)

### Debug Techniques

**Enable Verbose Logging:**
```python
logging.basicConfig(level=logging.DEBUG)
```

**Check Message Reception:**
```python
for i in range(100):
    count = interface.getSingleMessageReceivedSz("B6_...")
    print(f"Message count: {count}")
    time.sleep(0.1)
```

**Dump Field Value:**
```python
value = interface.getMessageFieldValue("B6_...", "field_name")
print(f"Raw value: {value!r}")  # Note: !r for repr
```

---

## Future Enhancements

### Potential Improvements

1. **Configuration File Support**
   - JSON-based test configuration
   - Scenario definitions
   - Field value expectations

2. **Data Logging**
   - CSV export of all field values
   - Time-series recording
   - Replay capability

3. **Enhanced Mock**
   - Load real message captures
   - Timing-accurate playback
   - Failure injection scenarios

4. **Integration with pybusmonitor1553**
   - Use your module as message source
   - UDP-based simulation
   - Full protocol stack testing

---

## References

### Related Files

- **C++ ICD Header:** `___OLD/_old/cpp/GrifoScope/GrifoSdkEif/pub/TH/th_b1553_icd.h`
- **Message Definitions:** `pybusmonitor1553/Grifo_E_1553lib/messages/`
- **ICD Documentation:** `doc/ICD-Implementation-Notes.md`

### Contact Information

For questions about this environment, refer to:
- Original test developers (Genova team)
- GRIFO radar system documentation
- MIL-STD-1553 protocol specifications

---

## Appendix: Quick Reference

### Command Line

```bash
# Run test with hardware
python GRIFO_M_PBIT.py

# Run test in simulation mode
python GRIFO_M_PBIT.py --simulate

# Change number of repetitions (edit script)
NUMBER_OF_REPETITIONS = 10
```

### Key Global Objects

```python
from leo_grifo_1553 import theGrifo1553          # 1553 interface
from leo_grifo_io_box import theBrainBox         # Power control
from leo_grifo_core import theRecorder           # Data recorder
```

### Common Code Patterns

```python
# Check field value
success, value, error = theGrifo1553.check(
    expected_value,
    message_name,
    field_name,
    timeout=5.0,
    step=0.1
)

# Power cycle
power_grifo_off(wait_after=3)
power_grifo_on(wait_after=0)

# Report section
report.open_session('Test Phase 1')
report.add_comment('Performing checks...')
report.close_session()
```

---

**End of Document**
