You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
8.7 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Triangulation Service
Сервис решает 3D-трилатерацию по 3 ресиверам:
- центры сфер: координаты ресиверов;
- радиусы сфер: расстояния, оцененные из RSSI с учетом частоты;
- расчет идет по одинаковым частотам, которые есть у всех 3 ресиверов;
- формируется таблица `frequency_table` (по каждой частоте отдельное решение);
- выбирается итоговая частота `selected_frequency_hz` по минимальному `rmse_m`.
## Что реализовано
- Автоматический polling 3 входных серверов (`http_sources`).
- Валидация входных payload с подробными ошибками.
- API:
- `GET /health`
- `GET /result`
- `GET /frequencies`
- `POST /refresh`
- `GET /config`
- `POST /config`
- UI (`/ui`) с:
- входными данными ресиверов;
- таблицей пересечений по частотам;
- итоговой позицией;
- статусом отправки на конечный сервер.
- Опциональный push результата на внешний сервер (`runtime.output_server`).
## Структура проекта
- [service.py](/c:/Users/snytk/triangulation/service.py) - автосервис + API + UI статик.
- [triangulation.py](/c:/Users/snytk/triangulation/triangulation.py) - математика.
- [config.template.json](/c:/Users/snytk/triangulation/config.template.json) - шаблон конфига.
- [web/index.html](/c:/Users/snytk/triangulation/web/index.html), [web/styles.css](/c:/Users/snytk/triangulation/web/styles.css), [web/app.js](/c:/Users/snytk/triangulation/web/app.js) - UI.
- [docker-compose.yml](/c:/Users/snytk/triangulation/docker-compose.yml) - test/prod профили.
- [docker/config.docker.test.json](/c:/Users/snytk/triangulation/docker/config.docker.test.json) - тестовый конфиг.
- [docker/mock_receiver.py](/c:/Users/snytk/triangulation/docker/mock_receiver.py) - mock входные сервера (random RSSI).
- [docker/mock_output_sink.py](/c:/Users/snytk/triangulation/docker/mock_output_sink.py) - mock конечный сервер.
## Docker Compose: test/prod режимы
`docker-compose.yml` разделен на профили:
- `test`:
- `triangulation-test`
- `receiver-r0`, `receiver-r1`, `receiver-r2`
- `output-sink`
- `prod`:
- `triangulation-prod` (читает ваш `./config.json`)
Это позволяет легко отключить тестовый режим и перейти на реальные сервера.
## Быстрый старт: Test Mode
Поднимает все контейнеры для end-to-end проверки:
- 3 входных mock сервера с random данными;
- основной сервис;
- output-sink, принимающий отправленные результаты.
```bash
docker compose --profile test up --build
```
Открыть:
- UI: `http://127.0.0.1:38081/ui`
- Полный результат: `http://127.0.0.1:38081/result`
- Частоты: `http://127.0.0.1:38081/frequencies`
- Полученные output-sink данные (изнутри сети контейнеров):
- `docker compose --profile test exec output-sink wget -qO- http://127.0.0.1:8080/latest`
Остановить:
```bash
docker compose --profile test down
```
## Быстрый старт: Prod Mode
1. Создайте `config.json` из шаблона:
```bash
cp config.template.json config.json
```
2. Заполните ваши реальные:
- `input.receivers[].source_url`
- `input.receivers[].center`
- `runtime.output_server`
3. Запустите:
```bash
docker compose --profile prod up --build
```
Доступ к API/UI в `prod`:
- `http://127.0.0.1:38082/ui`
- `http://127.0.0.1:38082/result`
- `http://127.0.0.1:38082/frequencies`
Остановить:
```bash
docker compose --profile prod down
```
## Как проверить, что данные приходят и отправляются
В UI (`/ui`) видно:
- блок `Ресиверы`: входящие samples;
- таблица `Таблица пересечений по частотам`: решения по каждой общей частоте;
- блок `Отправка на конечный сервер`: статус доставки (`ok/error`), HTTP-код, время, target.
Дополнительно:
- `GET /result` возвращает `output_delivery`.
- `GET /frequencies` тоже возвращает `output_delivery`.
- `docker compose --profile test logs output-sink -f` показывает факт приема.
- `GET /latest` на `output-sink` доступен изнутри docker-сети.
## Конфиг (основные поля)
Пример: [config.template.json](/c:/Users/snytk/triangulation/config.template.json)
Критичные поля:
- `input.mode`: только `"http_sources"` для автосервиса.
- `input.receivers`: ровно 3 ресивера.
- `input.aggregation`: `"median"` или `"mean"`.
- `runtime.poll_interval_s`: период опроса.
- `runtime.output_server.enabled`: push во внешний сервер.
## Формат входных payload
Поддержка:
- объект с `measurements`/`samples`/`data`;
- или сразу массив измерений.
Измерение:
- `frequency_hz` (или `freq_hz`/`frequency`/`freq`)
- `amplitude_dbm` (или `rssi_dbm`/`amplitude`/`rssi`)
Пример:
```json
{
"receiver_id": "r0",
"measurements": [
{ "frequency_hz": 433920000, "rssi_dbm": -61.5 },
{ "frequency_hz": 868100000, "rssi_dbm": -67.2 }
]
}
```
Если `receiver_id` передан, сервис сверяет его с ожидаемым receiver из конфига.
## Валидация и ошибки некорректного контекста
Проверяется:
- тип payload;
- наличие измерений;
- числовые и конечные значения;
- `frequency_hz > 0`;
- соответствие `receiver_id` при наличии;
- наличие общих частот у всех 3 ресиверов.
Ошибки содержат:
- `source_url=...`
- номер строки `row #...`
- проблемное поле.
## Тесты
Запуск:
```bash
pytest -q
```
Покрытие:
- математика триангуляции;
- влияние частоты на RSSI->distance;
- интеграция `AutoService.refresh_once()`;
- валидационные сценарии;
- ошибки контекста (нет общих частот, bad field, receiver mismatch, network error, output reject).
Файл интеграционных тестов:
- [test_service_integration.py](/c:/Users/snytk/triangulation/test_service_integration.py)
## Локальный запуск без Docker
```bash
python service.py --config config.json
```
UI:
- `http://127.0.0.1:38081/ui`
## Защита write-endpoints токеном
Для защиты изменений состояния можно задать токен в конфиге:
```json
{
"runtime": {
"write_api_token": "change-me"
}
}
```
После этого `POST /refresh` и `POST /config` требуют токен в одном из заголовков:
- `X-API-Token: <token>`
- `Authorization: Bearer <token>`
Что важно:
- `GET` endpoints остаются без токена.
- `GET /config` отдает `runtime.write_api_token` в редактированном виде (`""`) и флаг `write_api_token_set`.
- В UI во вкладке `Servers` есть поле `Write API token (session only)`:
- токен хранится только в памяти браузера;
- используется для `POST /refresh` и `POST /config`.
## Фильтры входных данных по каждому серверу
Для каждого ресивера в `input.receivers[]` можно задать `input_filter`:
```json
{
"input_filter": {
"enabled": true,
"min_frequency_mhz": 430.0,
"max_frequency_mhz": 440.0,
"min_rssi_dbm": -80.0,
"max_rssi_dbm": -40.0
}
}
```
Смысл:
- фильтр применяется отдельно к данным каждого ресивера до триангуляции;
- участвуют только измерения, попавшие в диапазоны частоты и RSSI;
- если после фильтрации у ресивера нет данных, цикл расчета возвращает ошибку.