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