Перейти до змісту

GPS-відстеження автопарку — Wialon Add-on

Модуль: Fleet → Processes → GPS Tracking → Fleet Map Статус: Платний аддон (plugin: "wialon_gps") Залежність: Активна підписка Wialon (Hosting або локальний сервер)


Зміст

  1. Архітектура
  2. Як це працює
  3. Структура файлів
  4. Активація аддону
  5. Налаштування Wialon токена
  6. Запуск і деплой
  7. WebSocket протокол
  8. Wialon API — деталі
  9. Troubleshooting
  10. Розширення і кастомізація

Архітектура

┌─────────────────────────────────────────────────────────────────┐
│  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

  1. Увійдіть у ваш Wialon аккаунт (хостинг або self-hosted)
  2. Меню → User Settings → вкладка Security
  3. Секція API TokensCreate token
  4. Виберіть:
  5. No expiry (або довгий термін)
  6. Права доступу: avl_unit — read, core — read
  7. Скопіюйте токен (показується лише раз)

Крок 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 (параметри останнього повідомлення):

{"lmsg": {"p": {"fuel_level": 45.5}}}
Назва ключа залежить від налаштування датчика у Wialon Admin. Шукаємо по черзі: fuel_level, fuel, Fuel Level, fuel_sensor, lvl_fuel.


Troubleshooting

Карта не показує ТЗ

  1. Перевірте лог Django: GPS broadcast: 0 units або помилку Wialon error
  2. Перевірте WIALON_TOKEN у .env
  3. Перевірте, чи ТЗ у Wialon мають активні GPS-трекери (останнє повідомлення < 24 год)
  4. Переконайтесь, що 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 не стартує

Перевірте лог запуску:

INFO fleet.apps: GPS broadcaster NOT started: WIALON_TOKEN is empty
→ Встановіть WIALON_TOKEN у .env.

INFO fleet.apps: GPSBroadcaster started
→ Все добре.

Якщо broadcaster стартував, але Wialon не відповідає:

ERROR fleet.wialon_service: Wialon login error: Wialon error 4: ...
→ Токен недійсний або прострочений. Створіть новий у Wialon.

Маркери не рухаються плавно

CSS-анімація (transition: transform 0.8s linear) спрацьовує лише якщо: - Браузер не блокує CSS transitions (режим економії батареї) - Вікно активне (background throttling у Chrome)


Розширення і кастомізація

Змінити інтервал polling

# .env
WIALON_POLL_INTERVAL=10   # 10 секунд (мінімум рекомендований: 10)

Кастомний значок ТЗ

Редагуйте 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