# Test Specification — GimbalDriver

## Acceptance Criteria Traceability

| AC ID | Acceptance Criterion | Test IDs | Coverage |
|-------|---------------------|----------|----------|
| AC-16 | Gimbal control sends pan/tilt/zoom commands to ViewPro A40 | IT-01, IT-02, AT-01 | Covered |
| AC-17 | Gimbal command latency ≤500ms from decision to physical movement | PT-01 | Covered |
| AC-18 | Zoom transitions: medium to high zoom within 2 seconds | IT-05, PT-02, AT-02 | Covered |
| AC-19 | Path-following accuracy: footpath stays within center 50% of frame | IT-06, AT-03 | Covered |
| AC-20 | Smooth gimbal transitions (no jerky movements) | IT-07, PT-03 | Covered |

---

## Integration Tests

### IT-01: Connect to UART (Mock TCP Mode)

**Summary**: Verify GimbalDriver connects to mock-gimbal service via TCP socket in dev mode.

**Traces to**: AC-16

**Input data**:
- Config: gimbal.mode=mock_tcp, mock_host=localhost, mock_port=9090
- Mock gimbal TCP server running

**Expected result**:
- connect() returns true
- is_alive() returns true
- get_state() returns GimbalState with valid initial values

**Max execution time**: 2s

**Dependencies**: Mock gimbal TCP server

---

### IT-02: set_angles Sends Correct ViewLink Command

**Summary**: Verify set_angles translates pan/tilt/zoom to a valid ViewLink serial packet.

**Traces to**: AC-16

**Input data**:
- Mock server that captures raw bytes received
- set_angles(pan=15.0, tilt=-30.0, zoom=20.0)

**Expected result**:
- Byte packet matches ViewLink protocol format (header, payload, checksum)
- Mock server acknowledges command
- set_angles returns true

**Max execution time**: 500ms

**Dependencies**: Mock gimbal server with byte capture

---

### IT-03: Connection Failure Returns False

**Summary**: Verify connect() returns false when UART/TCP port is unavailable.

**Traces to**: AC-16

**Input data**:
- Config: mock_port=9091 (no server listening)

**Expected result**:
- connect() returns false
- is_alive() returns false
- No crash or hang

**Max execution time**: 3s (with connection timeout)

**Dependencies**: None

---

### IT-04: Heartbeat Timeout Marks Gimbal Dead

**Summary**: Verify is_alive() returns false after 4 seconds without a heartbeat response.

**Traces to**: AC-16

**Input data**:
- Connected mock server that stops responding after initial connection
- Config: gimbal_timeout_s=4

**Expected result**:
- is_alive() returns true initially
- After 4s without heartbeat → is_alive() returns false

**Max execution time**: 6s

**Dependencies**: Mock gimbal server with configurable response behavior

---

### IT-05: zoom_to_poi Blocks Until Zoom Complete

**Summary**: Verify zoom_to_poi waits for zoom transition to complete before returning.

**Traces to**: AC-18

**Input data**:
- Current zoom=1.0, target zoom=20.0
- Mock server simulates 1.5s zoom transition

**Expected result**:
- zoom_to_poi blocks for ~1.5s
- Returns true after zoom completes
- get_state().zoom ≈ 20.0

**Max execution time**: 3s

**Dependencies**: Mock gimbal server with simulated zoom delay

---

### IT-06: follow_path PID Updates Direction

**Summary**: Verify follow_path computes PID output and sends angular velocity commands.

**Traces to**: AC-19

**Input data**:
- direction=(0.7, 0.3)
- pid_error=50.0 (pixels offset from center)
- PID gains: P=0.5, I=0.01, D=0.1

**Expected result**:
- Pan/tilt velocity commands sent to mock server
- Command magnitude proportional to error
- Returns true

**Max execution time**: 100ms

**Dependencies**: Mock gimbal server

---

### IT-07: PID Anti-Windup Clamps Integral

**Summary**: Verify the PID integral term does not wind up during sustained error.

**Traces to**: AC-20

**Input data**:
- 100 consecutive follow_path calls with constant pid_error=200.0
- PID integral clamp configured

**Expected result**:
- Integral term stabilizes at clamp value (does not grow unbounded)
- Command output reaches a plateau, no overshoot oscillation

**Max execution time**: 500ms

**Dependencies**: Mock gimbal server

---

### IT-08: Retry on Checksum Failure

**Summary**: Verify the driver retries up to 3 times when a checksum mismatch is detected.

**Traces to**: AC-16

**Input data**:
- Mock server returns corrupted response (bad checksum) on first 2 attempts, valid on 3rd

**Expected result**:
- set_angles retries 2 times, succeeds on 3rd
- Returns true
- If all 3 fail, raises GimbalCommandError

**Max execution time**: 500ms

**Dependencies**: Mock gimbal server with configurable corruption

---

### IT-09: return_to_sweep Resets Zoom to Medium

**Summary**: Verify return_to_sweep zooms out to medium zoom level and resumes sweep angle.

**Traces to**: AC-16

**Input data**:
- Current state: zoom=20.0, pan=15.0
- Expected return: zoom=1.0 (medium)

**Expected result**:
- Zoom command sent to return to medium zoom
- Returns true after zoom transition completes

**Max execution time**: 3s

**Dependencies**: Mock gimbal server

---

## Performance Tests

### PT-01: Command-to-Acknowledgement Latency

**Summary**: Measure round-trip time from set_angles call to server acknowledgement.

**Traces to**: AC-17

**Load scenario**:
- 100 sequential set_angles commands
- Mock server with 10ms simulated processing
- Duration: ~15s

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| Latency (p50) | ≤50ms | >500ms |
| Latency (p95) | ≤200ms | >500ms |
| Latency (p99) | ≤400ms | >500ms |

**Resource limits**:
- CPU: ≤10%
- Memory: ≤50MB

---

### PT-02: Zoom Transition Duration

**Summary**: Measure time from zoom command to zoom-complete acknowledgement.

**Traces to**: AC-18

**Load scenario**:
- 10 zoom transitions: alternating 1x→20x and 20x→1x
- Mock server with realistic zoom delay (1-2s)
- Duration: ~30s

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| Transition time (p50) | ≤1.5s | >2.0s |
| Transition time (p95) | ≤2.0s | >2.5s |

**Resource limits**:
- CPU: ≤5%

---

### PT-03: PID Follow Smoothness (Jerk Metric)

**Summary**: Measure gimbal command smoothness during path following by computing jerk (rate of acceleration change).

**Traces to**: AC-20

**Load scenario**:
- 200 PID updates at 10Hz (20s follow)
- Path with gentle curve (sinusoidal trajectory)
- Mock server records all received angular velocity commands

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| Max jerk (deg/s³) | ≤50 | >200 |
| Mean jerk (deg/s³) | ≤10 | >50 |

**Resource limits**:
- CPU: ≤15% (PID computation)

---

## Security Tests

### ST-01: UART Buffer Overflow Protection

**Summary**: Verify the driver handles oversized responses without buffer overflow.

**Traces to**: AC-16

**Attack vector**: Malformed or oversized serial response (EMI corruption, spoofing)

**Test procedure**:
1. Mock server sends response exceeding max expected packet size (e.g., 10KB)
2. Mock server sends response with invalid header bytes

**Expected behavior**: Driver discards oversized/malformed packets, logs warning, continues operation.

**Pass criteria**: No crash, no memory corruption; is_alive() still returns true after discarding bad packet.

**Fail criteria**: Buffer overflow, crash, or undefined behavior.

---

## Acceptance Tests

### AT-01: Pan/Tilt/Zoom Control End-to-End

**Summary**: Verify the full command cycle: set angles, read back state, verify match.

**Traces to**: AC-16

**Preconditions**:
- GimbalDriver connected to mock server

**Steps**:

| Step | Action | Expected Result |
|------|--------|-----------------|
| 1 | set_angles(pan=10, tilt=-20, zoom=5) | Returns true |
| 2 | get_state() | pan≈10, tilt≈-20, zoom≈5 (within tolerance) |
| 3 | set_angles(pan=-30, tilt=0, zoom=1) | Returns true |
| 4 | get_state() | pan≈-30, tilt≈0, zoom≈1 |

---

### AT-02: Zoom Transition Timing Compliance

**Summary**: Verify zoom from medium to high completes within 2 seconds.

**Traces to**: AC-18

**Preconditions**:
- GimbalDriver connected, current zoom=1.0

**Steps**:

| Step | Action | Expected Result |
|------|--------|-----------------|
| 1 | Record timestamp T0 | — |
| 2 | zoom_to_poi(pan=0, tilt=-10, zoom=20) | Blocks until complete |
| 3 | Record timestamp T1 | T1 - T0 ≤ 2.0s |
| 4 | get_state().zoom | ≈ 20.0 |

---

### AT-03: Path Following Keeps Error Within Bounds

**Summary**: Verify PID controller keeps path tracking error within center 50% of frame.

**Traces to**: AC-19

**Preconditions**:
- Mock server simulates gimbal with realistic response dynamics
- Trajectory: 20 waypoints along a curved path

**Steps**:

| Step | Action | Expected Result |
|------|--------|-----------------|
| 1 | Start follow_path with trajectory | PID commands issued |
| 2 | Simulate 100 PID cycles at 10Hz | Commands recorded by mock |
| 3 | Compute simulated frame-center error | Error < 50% of frame width for ≥90% of cycles |

---

## Test Data Management

**Required test data**:

| Data Set | Description | Source | Size |
|----------|-------------|--------|------|
| viewlink_packets | Reference ViewLink protocol packets for validation | Captured from spec / ArduPilot | ~10 KB |
| pid_trajectories | Sinusoidal and curved path trajectories for PID testing | Generated | ~5 KB |
| corrupted_responses | Oversized and malformed serial response bytes | Generated | ~1 KB |

**Setup procedure**:
1. Start mock-gimbal TCP server on configured port
2. Initialize GimbalDriver with mock_tcp config
3. Call connect()

**Teardown procedure**:
1. Call disconnect()
2. Stop mock-gimbal server

**Data isolation strategy**: Each test uses its own GimbalDriver instance connected to a fresh mock server. No shared state.
