GPS-відстеження автопарку — Wialon Add-on¶
Модуль: Fleet → Processes → GPS Tracking → Fleet Map
Статус: Платний аддон (plugin: "wialon_gps")
Залежність: Активна підписка Wialon (Hosting або локальний сервер)
Зміст¶
- Архітектура
- Як це працює
- Структура файлів
- Активація аддону
- Налаштування Wialon токена
- Запуск і деплой
- WebSocket протокол
- Wialon API — деталі
- Troubleshooting
- Розширення і кастомізація
Архітектура¶
┌─────────────────────────────────────────────────────────────────┐
│ Wialon Hosting / Self-Hosted │
│ https://hst-api.wialon.com/wialon/ajax.html │
└────────────────────────┬────────────────────────────────────────┘
│ HTTP POST (core/search_items)
│ кожні 15 секунд
┌────────────────────────▼────────────────────────────────────────┐
│ Django Backend │
│ │
│ ┌──────────────────┐ ┌─────────────────────────────────┐ │
│ │ WialonService │────▶│ GPSBroadcaster (daemon thread) │ │
│ │ wialon_service │ │ gps_broadcaster.py │ │
│ │ .py │ └──────────────┬──────────────────┘ │
│ └──────────────────┘ │ async_to_sync │
│ │ channel_layer │
│ ┌───────────────────────────────────────▼──────────────────┐ │
│ │ InMemoryChannelLayer │ │
│ │ group: "gps_fleet_updates" │ │
│ └───────────────────────────────────────┬──────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────▼──────────────────┐ │
│ │ GPSConsumer (AsyncWebsocketConsumer) │ │
│ │ ws/fleet/gps/?token=<JWT> │ │
│ └───────────────────────────────────────┬──────────────────┘ │
└──────────────────────────────────────────┼─────────────────────-┘
│ WebSocket
┌──────────────────────────────────────────▼──────────────────────┐
│ React Frontend (DOP) │
│ │
│ useGpsFleetSocket() │
│ ├── WebSocket connection + auto-reconnect │
│ └── state: { units: Record<id, GpsUnit>, connected, ... } │
│ │
│ GpsFleetMap.tsx │
│ ├── react-leaflet MapContainer + TileLayer (OpenStreetMap) │
│ ├── Marker per unit (SVG arrow rotated to course) │
│ └── Status sidebar (connected/moving/idle counts) │
└──────────────────────────────────────────────────────────────────┘
Ключові рішення¶
| Питання | Рішення |
|---|---|
| Без Redis | InMemoryChannelLayer — всі WS-клієнти в одному процесі |
| Без Celery | threading.Thread(daemon=True) — фоновий потік у Django |
| Один запит до Wialon | core/search_items з count=1000 — всі ТЗ за раз |
| Авторизація WS | JWT у query string ?token=<access_token> |
| Плавний рух маркерів | CSS transition: transform 0.8s linear на .leaflet-marker-icon |
Як це працює¶
1. Стартап сервера¶
При запуску Django (daphne / runserver) FleetConfig.ready() перевіряє:
- чи встановлено WIALON_TOKEN у .env
- чи це дійсно серверний процес (не migrate, shell тощо)
Якщо все ок — стартує GPSBroadcaster.start_once().
2. Polling loop (кожні 15 с)¶
units = WialonService().fetch_units()
# → POST https://hst-api.wialon.com/wialon/ajax.html
# svc=core/search_items flags=4609
# → [{id, nm, pos:{x,y,s,c,t}, lmsg:{p:{fuel_level}}}]
channel_layer.group_send("gps_fleet_updates", {
"type": "gps.update",
"units": parsed_units,
})
3. GPSConsumer → WebSocket¶
Кожен підключений браузер отримує {"type":"gps_update","units":[...]} через WS.
4. Frontend¶
useGpsFleetSocket() → units: Record<id, GpsUnit>
GpsFleetMap → Marker per unit, auto-fit bounds on first data
Структура файлів¶
backend/
└── fleet/
├── wialon_service.py # WialonService: login, fetch_units, parse
├── gps_broadcaster.py # GPSBroadcaster: daemon thread + singleton
├── gps_consumer.py # GPSConsumer: AsyncWebsocketConsumer
├── apps.py # auto-start broadcaster in ready()
└── management/
└── commands/
└── start_gps_poller.py # python manage.py start_gps_poller
backend/eswf_chat/routing.py # додано ws/fleet/gps/ URL
backend/eswf/settings/base.py # WIALON_TOKEN, WIALON_API_URL, WIALON_POLL_INTERVAL
frontend/erp/src/
├── hooks/
│ └── useGpsFleetSocket.ts # WS hook: connects, auto-reconnects, state
└── components/Fleet/
├── GpsFleetMap.tsx # React map component (react-leaflet)
└── GpsFleetMap.css # leaflet overrides + vehicle marker styles
frontend/erp/src/config/fleet.ts # додано group "gpstracking" + item "gpsFleet"
frontend/erp/src/i18n/locales/ # додано ключі "gps.*" (en + ua)
Активація аддону¶
Крок 1. Отримати Wialon token¶
- Увійдіть у ваш Wialon аккаунт (хостинг або self-hosted)
- Меню → User Settings → вкладка Security
- Секція API Tokens → Create token
- Виберіть:
- No expiry (або довгий термін)
- Права доступу:
avl_unit— read,core— read - Скопіюйте токен (показується лише раз)
Крок 2. Додати токен до .env¶
# backend/.env (або c:\eswf\backend\.env)
WIALON_TOKEN=5b88bf37fef72dab4a42d39d710bd5e21946B1D9...
WIALON_API_URL=https://hst-api.wialon.com/wialon/ajax.html
WIALON_POLL_INTERVAL=15
Self-hosted Wialon: замініть
WIALON_API_URLна URL вашого сервера:WIALON_API_URL=https://your-wialon.company.com/wialon/ajax.html
Крок 3. Встановити Python-залежності¶
cd c:\eswf\backend
pip install requests
# requests вже є в більшості проєктів, але перевірте requirements.txt
Крок 4. Встановити npm-пакети¶
cd c:\eswf\frontend\erp
npm install
# Встановить leaflet, react-leaflet, @types/leaflet (додані до package.json)
Крок 5. Перезапустити сервер¶
# Backend (daphne або runserver)
cd c:\eswf\backend
python manage.py runserver
# Або через launcher.js:
node c:\eswf\launcher.js
При старті в логах має з'явитись:
INFO fleet.apps: GPSBroadcaster started (poll_interval=15s, group=gps_fleet_updates)
INFO fleet.gps_broadcaster: GPSBroadcaster thread running (interval=15s)
INFO fleet.wialon_service: Wialon login OK eid=a1b2c3d4... user=your@account.com
Крок 6. Відкрити в DOP¶
Fleet → Processes → GPS Tracking → Fleet Map
Налаштування Wialon токена¶
Права токена¶
| Об'єкт | Потрібні права |
|---|---|
| avl_unit | View (read) |
| avl_unit — properties | View |
| avl_unit — last message | View |
Мінімальних прав достатньо. Права на запис НЕ потрібні.
Scoped token (обмежений доступ)¶
Якщо ваш Wialon аккаунт має доступ до багатьох об'єктів, можна створити токен лише з доступом до конкретних ТЗ через Access у налаштуваннях токена.
Відповідність ТЗ у Wialon та ESWF¶
Wialon повертає nm (name) — це ім'я об'єкта у Wialon.
Для прив'язки до Vehicle у ESWF: nm має збігатись з license_plate або name.
Автоматичного зв'язування немає — карта показує всі ТЗ з Wialon незалежно від БД.
Запуск і деплой¶
Автоматичний старт (рекомендовано)¶
Broadcaster стартує автоматично в FleetConfig.ready() при запуску ASGI-сервера.
Жодних додаткових дій не потрібно.
Ручний запуск (альтернатива)¶
# Стартувати polling як окремий процес (наприклад, окремий Docker container)
python manage.py start_gps_poller
python manage.py start_gps_poller --interval 20
# Або через systemd:
# /etc/systemd/system/eswf-gps-poller.service
# ExecStart=/opt/eswf/venv/bin/python manage.py start_gps_poller
Важливо: не запускайте
start_gps_pollerодночасно з основним сервером, якщо обидва використовуютьInMemoryChannelLayer— вони в різних процесах і не бачать один одного! Або використовуйте Redis, або лише один процес.
Production з Redis¶
Якщо переходите на Redis (для multi-process / multi-worker):
# settings/production.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [("127.0.0.1", 6379)]},
}
}
У цьому випадку start_gps_poller може бути окремим сервісом.
WebSocket протокол¶
Підключення¶
ws://localhost:8000/ws/fleet/gps/?token=<JWT_ACCESS_TOKEN>
wss://erp.eswf.dev/ws/fleet/gps/?token=<JWT_ACCESS_TOKEN>
JWT отримується стандартним логіном через /api/v1/token/.
Повідомлення сервер → клієнт¶
{
"type": "gps_update",
"units": [
{
"id": 12345,
"name": "АА 1234 BB",
"lat": 50.45,
"lon": 30.52,
"speed": 65,
"course": 180,
"altitude": 120,
"timestamp": 1700000000,
"fuel": 45.5
}
]
}
Повідомлення надходять кожні WIALON_POLL_INTERVAL секунд.
fuel = null, якщо датчик пального не налаштований у Wialon.
Коди закриття¶
| Код | Причина |
|---|---|
| 4001 | Відхилено — недійсний або відсутній JWT |
| 1000 | Нормальне закриття |
Wialon API — деталі¶
Метод¶
POST https://hst-api.wialon.com/wialon/ajax.html
Content-Type: application/x-www-form-urlencoded
svc=core/search_items
sid=<eid>
params={"spec":{"itemsType":"avl_unit","propName":"sys_name","propValueMask":"*","sortType":"sys_name"},"force":1,"flags":4609,"from":0,"count":1000}
Flags = 4609 розшифровка¶
4609 = 1 + 8 + 4096 + 512
1 = item base (id, nm, cls)
8 = avl_unit base info (type, hw)
512 = admin fields
4096 = last message + position (pos.x, pos.y, pos.s, pos.c, pos.t, lmsg)
Автентифікація¶
POST ...
svc=token/login
params={"token":"<YOUR_LONG_LIVED_TOKEN>","fl":1}
→ {"eid":"<session_id>","au":"username",...}
eid зберігається в WialonService._eid і використовується для всіх наступних запитів.
При error=1 або error=4 — автоматичний relogin.
Паливо¶
Значення пального береться з lmsg.p (параметри останнього повідомлення):
fuel_level, fuel, Fuel Level, fuel_sensor, lvl_fuel.
Troubleshooting¶
Карта не показує ТЗ¶
- Перевірте лог Django:
GPS broadcast: 0 unitsабо помилкуWialon error - Перевірте
WIALON_TOKENу.env - Перевірте, чи ТЗ у Wialon мають активні GPS-трекери (останнє повідомлення < 24 год)
- Переконайтесь, що Token API увімкнено у Wialon (User Settings → Security → API Access)
WebSocket не підключається (4001)¶
- Недійсний або прострочений JWT-токен у frontend
- Перевірте, чи правильно передається
?token=у URL
WebSocket не підключається (без відповіді)¶
- Перевірте, чи запущений Daphne (а не звичайний wsgi runserver)
python manage.py runserverНЕ підтримує WebSocket — потрібенdaphne- Або налаштуйте Nginx з upgrade headers для wss://
GPSBroadcaster не стартує¶
Перевірте лог запуску:
→ ВстановітьWIALON_TOKEN у .env.
→ Все добре.
Якщо broadcaster стартував, але Wialon не відповідає:
→ Токен недійсний або прострочений. Створіть новий у Wialon.Маркери не рухаються плавно¶
CSS-анімація (transition: transform 0.8s linear) спрацьовує лише якщо:
- Браузер не блокує CSS transitions (режим економії батареї)
- Вікно активне (background throttling у Chrome)
Розширення і кастомізація¶
Змінити інтервал polling¶
Кастомний значок ТЗ¶
Редагуйте makeVehicleIcon() у GpsFleetMap.tsx.
Функція отримує course (напрямок) і speed — повертає L.DivIcon з SVG.
Фільтрувати ТЗ по tenant¶
Відредагуйте WialonService.fetch_units() — додайте фільтрацію по nm (назва ТЗ) або використовуйте Wialon Groups (resource/group items).
Зберігати GPS-треки в БД¶
Додайте модель GpsTrack у fleet/models/ і зберігайте дані в GPSBroadcaster.run() перед group_send.
Мультитенантний Wialon¶
Для різних токенів per-tenant: розширте WialonService щоб приймати token у конструкторі і стартуйте по одному broadcaster per tenant у FleetConfig.ready().
Вартість і ліцензування¶
| Компонент | Вартість |
|---|---|
| Wialon Hosting | За тарифом Wialon (per unit/month) |
| ESWF Wialon Add-on | Активується ліцензійним ключем у Store Manager |
| OpenStreetMap tiles | Безкоштовно (для некомерційного / помірного використання) |
Для комерційного production: розгляньте платні тайли (Mapbox, Google Maps) або self-hosted MapTiler для відповідності TOS OpenStreetMap.
Документ актуальний для ESWF v1.0, Fleet add-on Wialon GPS v1.0