PlatSim_Genova/GRIFO_Test_Environment_Analysis.md
2026-01-30 16:38:33 +01:00

868 lines
22 KiB
Markdown

# 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**