SXXXXXXX_PyBusMonitor1553/doc/UDP1553-Protocol-Implementation.md
2025-12-17 07:59:30 +01:00

348 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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