# 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: ` - `Authorization: Bearer ` Что важно: - `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; - если после фильтрации у ресивера нет данных, цикл расчета возвращает ошибку.