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