# Испытательный UDP-протокол

## 1. Назначение

Этот backend нужен для испытаний, когда проект должен отправлять состояние цели в блок наведения по UDP в локальной сети.

В проекте этот режим включается через:

```text
AUTOPILOT_BACKEND = "proto_udp"
PROTO_UDP_ENABLE = True
```

или через env:

```text
FPV_AUTOPILOT_BACKEND=proto_udp
FPV_PROTO_UDP_ENABLE=1
```

## 2. Куда это встроено

Реализация находится в:

- `autopilot_bridge.py`

Новый backend называется:

- `proto_udp`

## 3. Транспорт

- протокол: UDP
- адрес: `PROTO_UDP_HOST`
- порт: `PROTO_UDP_PORT`

По умолчанию:

```python
PROTO_UDP_HOST = "127.0.0.1"
PROTO_UDP_PORT = 5005
```

## 4. Формат пакета

Формат упаковки:

```text
<BBBhhbbB
```

Это little-endian бинарный пакет фиксированной длины.

Поля идут в таком порядке:

1. `descriptor`
2. `object_id`
3. `target_state`
4. `offset_y_px`
5. `offset_x_px`
6. `offset_y_pct`
7. `offset_x_pct`
8. `bbox_area_pct`

## 5. Таблица полей

### `descriptor`

- тип: `uint8`
- стандартное значение: `1`
- задается через `PROTO_UDP_DESCRIPTOR`

### `object_id`

- тип: `uint8`
- диапазон: `0..255`
- `0`, если цели нет

### `target_state`

- тип: `uint8`
- значения:
  - `0` — цели нет
  - `1` — цель есть, идет обычная детекция/сопровождение
  - `3` — детекция потеряна, но проект удерживает цель трекингом

Значение `2` в пользовательском протоколе зарезервировано под сценарий "несколько целей". Текущая реализация проекта его не отправляет, потому что runtime всегда выбирает одну основную цель.

### `offset_y_px`

- тип: `int16`
- знак:
  - вверх `+`
  - вниз `-`

### `offset_x_px`

- тип: `int16`
- знак:
  - вправо `+`
  - влево `-`

### `offset_y_pct`

- тип: `int8`
- диапазон: `-100..100`
- считается относительно половины высоты кадра

### `offset_x_pct`

- тип: `int8`
- диапазон: `-100..100`
- считается относительно половины ширины кадра

### `bbox_area_pct`

- тип: `uint8`
- диапазон: `0..100`
- отношение площади bbox к площади кадра в процентах

## 6. Что отправляется при отсутствии цели

Если цели нет, пакет отправляется с нулями:

- `object_id = 0`
- `target_state = 0`
- все offsets = `0`
- `bbox_area_pct = 0`

Это соответствует вашему описанию:

> если цели нет, все значения остаются нулевыми

## 7. Как вычисляются значения

### Смещение в пикселях

Берется из guidance-точки:

- `aim_x`
- `aim_y`

Относительно центра кадра:

- `offset_x_px = aim_x - center_x`
- `offset_y_px = center_y - aim_y`

То есть знак сделан так, чтобы:

- вправо было положительным;
- вверх было положительным.

### Смещение в процентах

Нормируется к половине размера кадра:

- по X к `frame_w / 2`
- по Y к `frame_h / 2`

И затем ограничивается до `-100..100`.

### Площадь bbox

Используются:

- `box_w`
- `box_h`
- `frame_w`
- `frame_h`

Формула:

```text
bbox_area_pct = 100 * (box_w * box_h) / (frame_w * frame_h)
```

## 8. Включение в локальном запуске

### PowerShell

```powershell
$env:FPV_AUTOPILOT_BACKEND = "proto_udp"
$env:FPV_PROTO_UDP_ENABLE = "1"
$env:FPV_PROTO_UDP_HOST = "192.168.1.50"
$env:FPV_PROTO_UDP_PORT = "5005"
python main.py
```

### Bash

```bash
export FPV_AUTOPILOT_BACKEND=proto_udp
export FPV_PROTO_UDP_ENABLE=1
export FPV_PROTO_UDP_HOST=192.168.1.50
export FPV_PROTO_UDP_PORT=5005
python3 main.py
```

## 9. Включение в Docker

В `docker-compose.yml`:

```yaml
FPV_AUTOPILOT_BACKEND: "proto_udp"
FPV_PROTO_UDP_ENABLE: "1"
FPV_PROTO_UDP_HOST: "192.168.1.50"
FPV_PROTO_UDP_PORT: "5005"
```

## 10. Пример приема пакета на Python

```python
import socket
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 5005))

while True:
    data, addr = sock.recvfrom(1024)
    descriptor, object_id, target_state, off_y_px, off_x_px, off_y_pct, off_x_pct, bbox_area_pct = struct.unpack(
        "<BBBhhbbB", data
    )
    print(addr, descriptor, object_id, target_state, off_y_px, off_x_px, off_y_pct, off_x_pct, bbox_area_pct)
```

## 11. Что важно проверить на стенде

1. Совпадает ли порядок байтов с ожидаемым приемником.
2. Совпадает ли знак вертикали и горизонтали.
3. Устраивает ли блок значение `target_state = 3` при удержании без детекции.
4. Не нужен ли вам в будущем отдельный режим `target_state = 2`.
