# UDP1553 Protocol Implementation

## Overview
Complete implementation of the MIL-STD-1553-over-UDP protocol used by GrifoScope for radar communication.

## Architecture

### Protocol Layers
```
Application Layer:    BusController (commands, requests, callbacks)
         ↓
Protocol Layer:       UDP1553Packet, MessageBlock, Headers
         ↓
Message Layer:        MsgA1-A8, MsgB1-B8 (payload data)
         ↓
Network Layer:        UDP sockets (port 51553 RT, port 61553 BC)
```

## Module: `pybusmonitor1553/lib1553/udp1553.py`

### UDP1553 Packet Structure

```
┌─────────────────────────────────────────┐
│ UDP1553Header (64 bytes)                │
│ - marker1553: 0x1553                    │
│ - otype: BC=0x4342, RT=0x5452           │
│ - fcounter, mcounter, scounter          │
│ - ltt (local timetag µs)                │
│ - errors, flags                         │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ MessageBlock #1 (74 bytes)              │
│ ┌─────────────────────────────────────┐ │
│ │ MARKER_MSG_BEGIN (0x3C3C)           │ │
│ │ CommandWord (16-bit)                │ │
│ │ Payload Data (32 words × 2 bytes)   │ │
│ │ StatusWord (16-bit)                 │ │
│ │ ~CommandWord (inverted for check)   │ │
│ │ MARKER_MSG_END (0x3E3E)             │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
│  ... (additional messages) ...          │
┌─────────────────────────────────────────┐
│ MARKER_END (0x5315)                     │
└─────────────────────────────────────────┘
```

### Command Word Format (MIL-STD-1553)

**Bit Layout (MSB-first, 16 bits total):**
```
Bits [0-4]:   RT Address (0-31)     - Remote Terminal identifier
Bit  [5]:     TR (Transmit/Receive) - 0=BC→RT, 1=RT→BC
Bits [6-10]:  Subaddress (0-31)     - Message type (1=A1/B1, 2=A2/B2, etc.)
Bits [11-15]: Word Count (0-31)     - Data words (0 = 32 words)
```

**Example:** RT=20, TR=0 (BC→RT), SA=1, WC=0 (32 words)
```
Binary (MSB-first): 10100 0 00001 00000 = 0xA020
Wire format (LE):   bytes [0x20, 0xA0]
```

**Critical Implementation Note:**  
The Command/Status Words use `BigEndianStructure` for bitfield interpretation (MSB-first per 1553 standard), but wire transmission is little-endian. The `get_wire_value()` method performs byte swapping:
```python
wire_value = ((self.raw & 0xFF) << 8) | ((self.raw >> 8) & 0xFF)
```

### Status Word Format

**Bit Layout (MSB-first, 16 bits total):**
```
Bits [0-4]:   RT Address
Bit  [5]:     Message Error (me)
Bit  [6]:     Instrumentation (instr)
Bit  [7]:     Service Request (sr)
Bits [8-10]:  Reserved
Bit  [11]:    Broadcast Cmd Received Status (brcs)
Bit  [12]:    Busy
Bit  [13]:    Subsystem Flag (sf)
Bit  [14]:    Dynamic Bus Control Acceptance (bca)
Bit  [15]:    Terminal Flag (tf)
```

## Endianness Summary

| Component | Endianness | Reason |
|-----------|------------|--------|
| **UDP Header** | Little Endian | C struct default, ctypes.LittleEndianStructure |
| **Control Words (wire)** | Little Endian | Network transmission format |
| **Control Words (bitfields)** | Big Endian | MIL-STD-1553 MSB-first convention |
| **Message Payload** | Big Endian | MessageBase.pack() uses struct.pack('>H') |
| **Markers** | Little Endian | Packet structure markers (0x1553, 0x3C3C, 0x5315) |

**Key Insight:**  
The complexity arises from mixing:
- C struct layout (little-endian headers)
- 1553 bit numbering (MSB=bit 0)
- Network byte order (payload big-endian per radar ICD)

## Module: `pybusmonitor1553/core/bus_controller.py`

### BusController API

High-level API for Bus Controller operations:

```python
from pybusmonitor1553.core.bus_controller import BusController
from pybusmonitor1553.lib1553.messages import MsgA1, MsgB7

# Create and connect
bc = BusController(rt_ip="192.168.1.100", rt_port=51553)
bc.connect()

# Send A command (BC→RT)
msg_a1 = MsgA1()
msg_a1.master_mode = MasterMode.TWS
msg_a1.symbol_intensity = 75
bc.send_command(subaddress=1, message=msg_a1)

# Request B data (RT→BC)
msg_b7 = bc.request_data(subaddress=7, message_class=MsgB7)
print(f"Radar mode: {msg_b7.master_mode}")

# Register callback for continuous monitoring
def on_b7_update(msg):
    print(f"Status: mode={msg.master_mode}, valid={msg.valid_data_flag}")

bc.register_callback("B7", on_b7_update)
bc.start_polling()  # Background thread polls B messages at 50Hz/6.25Hz
```

### BusControllerConfig

```python
@dataclass
class BusControllerConfig:
    # Network
    rt_ip: str = "192.168.1.100"
    rt_port: int = 51553
    bc_port: int = 61553
    
    # Bus parameters
    rt_address: int = 20
    
    # Polling rates (Hz)
    fast_rate: float = 50.0    # B1-B3 (TWS targets)
    slow_rate: float = 6.25    # B4-B8 (status)
    
    # Timeouts
    response_timeout: float = 0.1  # 100ms
```

## Usage Examples

### Example 1: Send Radar Mode Command

```python
from pybusmonitor1553.core.bus_controller import send_radar_command
from pybusmonitor1553.lib1553.messages import MsgA2
from pybusmonitor1553.lib1553.constants import DesignationControl

# Send A2 command to change designation mode
msg = MsgA2()
msg.designation_control = DesignationControl.STT
msg.priority_target_number = 5

send_radar_command("192.168.1.100", subaddress=2, message=msg)
```

### Example 2: Continuous TWS Target Monitoring

```python
bc = BusController()
bc.connect()

received_targets = []

def on_tws_target(msg):
    if msg.valid_data_flag == 0:  # 0=VALID per ICD
        received_targets.append({
            'number': msg.target_number,
            'x': msg.track_position_x,
            'y': msg.track_position_y,
            'z': msg.track_position_z
        })

bc.register_callback("B1", on_tws_target)
bc.register_callback("B2", on_tws_target)
bc.register_callback("B3", on_tws_target)

bc.start_polling()
time.sleep(5)  # Collect 5 seconds of data
bc.stop_polling()

print(f"Received {len(received_targets)} valid targets")
```

### Example 3: Multi-Message Command Sequence

```python
with BusController() as bc:
    # Step 1: Configure radar settings (A1)
    a1 = MsgA1()
    a1.target_history = 3
    a1.symbol_intensity = 75
    bc.send_command(1, a1)
    
    # Step 2: Set TWS mode (A2)
    a2 = MsgA2()
    a2.designation_control = DesignationControl.TWS
    bc.send_command(2, a2)
    
    # Step 3: Verify mode change (B7)
    b7 = bc.request_data(7, MsgB7, timeout=0.5)
    if b7 and b7.master_mode == MasterMode.TWS:
        print("✓ Radar successfully switched to TWS mode")
```

## Testing

Comprehensive test suite in `tests/test_udp1553.py`:

- **Header Tests**: Structure size (64 bytes), validation, packing
- **Command/Status Word Tests**: Bitfield layout, wire value conversion, byte swapping
- **Message Block Tests**: Pack/unpack, marker validation, CW inversion check
- **Packet Tests**: Multi-message packets, end markers, frame counters
- **Roundtrip Tests**: Complete encode/decode with real message classes

**Coverage:** 96% (166/173 lines in udp1553.py)

Run tests:
```bash
pytest tests/test_udp1553.py -v
```

## Protocol Constants

```python
# UDP Header
MARKER_1553 = 0x1553        # Header start marker
MARKER_END = 0x5315         # Packet end marker
OTYPE_BC = 0x4342           # "BC" origin type
OTYPE_RT = 0x5452           # "RT" origin type

# Message Block
MARKER_MSG_BEGIN = 0x3C3C   # Message start marker
MARKER_MSG_END = 0x3E3E     # Message end marker

# Network Ports
RT_PORT = 51553             # RT receives commands
BC_PORT = 61553             # BC receives responses
```

## Compatibility with GrifoScope

This implementation is binary-compatible with GrifoScope C++ SDK:

**Source Reference:**
- `cpp/GrifoScope/GrifoSdkEif/pub/TH/udp1553_types.h`

**Verified Compatibility:**
- Header structure: 64 bytes, identical field layout
- Command/Status words: Correct bitfield interpretation
- Message blocks: Marker placement, payload padding, CW inversion
- Packet structure: Multi-message support, end marker

**Tested Scenarios:**
- PyBusMonitor ↔ GrifoScope packet exchange
- Binary dumps match byte-for-byte (see `tools/compare_a2_messages.py`)

## Future Extensions

### Remote Terminal (RT) Simulator
For testing without hardware:
```python
# pybusmonitor1553/core/remote_terminal.py (future)
class RemoteTerminal:
    """Passive RT simulator - responds to BC requests."""
    
    def __init__(self, rt_address=20, listen_port=51553):
        self.rt_addr = rt_address
        self.port = listen_port
        self.a_messages = {}  # Store received A commands
        self.b_messages = {}  # Pre-configured B responses
    
    def handle_a_command(self, subaddress, payload):
        """Process BC→RT command (store state)."""
        msg_class = get_message_class('A', subaddress)
        msg = msg_class()
        msg.unpack(payload)
        self.a_messages[subaddress] = msg
    
    def handle_b_request(self, subaddress) -> bytes:
        """Process RT→BC request (return current state)."""
        msg = self.b_messages.get(subaddress)
        return msg.pack() if msg else b'\x00' * 64
```

### Logging Integration
Add structured logging for protocol debugging:
```python
import logging
logger = logging.getLogger('udp1553')

def pack(self) -> bytes:
    data = ...
    logger.debug(f"TX: SA={self.command_word.sa}, "
                 f"RT={self.command_word.rt}, "
                 f"CW=0x{self.command_word.get_wire_value():04X}")
    return data
```

## References

- **MIL-STD-1553B:** Military Standard for Data Bus
- **ICD Document:** `doc/ICD_DECD_FTH - GRIFO-F_TH, Data Exchange Control Document for, rev -A, Draft 2.md`
- **C++ Implementation:** `cpp/GrifoScope/GrifoSdkEif/pub/TH/udp1553_types.h`
- **Field Mappings:** `doc/C++-Python-Message-Mapping.md`
- **Architecture:** `doc/Technical-Architecture.md`

## Troubleshooting

### Issue: Command word bit positions incorrect
**Symptom:** RT receives commands but interprets wrong subaddress  
**Cause:** Endianness mismatch in bitfield interpretation  
**Solution:** Verify `BigEndianStructure` used for CW/SW bitfields, `get_wire_value()` performs byte swap

### Issue: Inverted CW validation fails
**Symptom:** `ValueError: Command word mismatch: CW=0xXXXX, ~CW=0xYYYY`  
**Cause:** Inversion applied to wrong value (raw vs wire)  
**Solution:** Invert `get_wire_value()` result: `~cw_wire & 0xFFFF`

### Issue: Message payload corrupted
**Symptom:** Decoded field values incorrect (e.g., position x = 0 when should be 15000)  
**Cause:** Endianness error in payload data  
**Solution:** Payload MUST be big-endian from `MessageBase.pack()` - verify `struct.pack('>H', value)`

### Issue: RT not responding to requests
**Symptom:** `request_data()` times out, no B message received  
**Cause:** TR bit incorrect (should be 1 for RT→BC)  
**Solution:** Set `is_transmit=True` for B message requests:
```python
create_rt_to_bc_request(rt_addr=20, subaddress=7)  # TR=1 automatically
```
