Traccar API Testing Workbook

Instancja: traccar.vps-i1.infra.zintegrowana.online (kontener traccar na vps-i1)
Port lokalny: 8082 (dostęp przez SSH tunnel / localhost)
Wersja: Traccar 6.x
Auth: POST /api/session (session cookie) — hasła czytane z .env.local, nigdy w chacie LLM
Biblioteka: Python + paramiko (SSH → vps-i1 → curl localhost:8082)


Wzorzec autentykacji

Wszystkie przykłady używają tego samego wzorca: SSH do vps-i1, a następnie curl do localhost:8082. Hasła nigdy nie trafiają do chatu LLM — czytane z .env.local.

import paramiko
import json
import urllib.parse
import sys
 
def get_traccar_client():
    """
    Czyta credentials z .env.local, otwiera SSH do vps-i1,
    loguje się do Traccar API i zwraca (client, run_fn, cookie_path).
 
    Hasła NIE trafiają do chatu LLM — czytane z pliku.
    """
    # 1. Odczyt credentials z .env.local
    env = {}
    with open(r'C:\code_2026\p24-infra\.env.local') as f:
        for line in f:
            line = line.strip()
            if '=' in line and not line.startswith('#'):
                k, v = line.split('=', 1)
                env[k.strip()] = v.strip().strip('"').strip("'")
 
    traccar_user = env['TRACCAR_USER']
    traccar_password = env['TRACCAR_PASSWORD']
    # TRACCAR_URL np. http://localhost:8082 lub https://traccar.vps-i1.infra.zintegrowana.online
 
    # 2. SSH do vps-i1
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(
        '217.154.82.162', port=22, username='root',
        key_filename=r'C:\Users\konar\.ssh\id_ed25519',
        timeout=15
    )
 
    def run(cmd):
        stdin, stdout, stderr = client.exec_command(cmd)
        out = stdout.read().decode().strip()
        err = stderr.read().decode().strip()
        return out, err
 
    # 3. Login — tworzy plik cookie /tmp/traccar.cookies
    login_data = urllib.parse.urlencode({
        'email': traccar_user,
        'password': traccar_password
    })
    out, err = run(
        f"curl -s -c /tmp/traccar.cookies -X POST http://localhost:8082/api/session "
        f"-H 'Content-Type: application/x-www-form-urlencoded' "
        f"-d '{login_data}'"
    )
    session = json.loads(out)
    print(f"Zalogowano jako: {session.get('name', 'unknown')} (id={session.get('id')})")
 
    return client, run, '/tmp/traccar.cookies'
 
 
# Helper: wykonaj GET request z session cookie
def traccar_get(run, endpoint, params=None):
    url = f"http://localhost:8082/api{endpoint}"
    if params:
        url += '?' + urllib.parse.urlencode(params)
    out, err = run(f"curl -s -b /tmp/traccar.cookies '{url}'")
    return json.loads(out)
 
 
# Helper: wykonaj POST request z session cookie
def traccar_post(run, endpoint, body):
    url = f"http://localhost:8082/api{endpoint}"
    body_json = json.dumps(body)
    # Escape single quotes w body_json
    body_escaped = body_json.replace("'", "'\\''")
    out, err = run(
        f"curl -s -b /tmp/traccar.cookies -X POST '{url}' "
        f"-H 'Content-Type: application/json' "
        f"-d '{body_escaped}'"
    )
    return json.loads(out)
 
 
# Helper: wykonaj PUT request
def traccar_put(run, endpoint, body):
    url = f"http://localhost:8082/api{endpoint}"
    body_json = json.dumps(body)
    body_escaped = body_json.replace("'", "'\\''")
    out, err = run(
        f"curl -s -b /tmp/traccar.cookies -X PUT '{url}' "
        f"-H 'Content-Type: application/json' "
        f"-d '{body_escaped}'"
    )
    return json.loads(out)
 
 
# Helper: wykonaj DELETE request
def traccar_delete(run, endpoint):
    url = f"http://localhost:8082/api{endpoint}"
    out, err = run(
        f"curl -s -b /tmp/traccar.cookies -X DELETE '{url}'"
    )
    return out  # DELETE zwraca pusty body lub 204

Sekcja 1: Przeglądanie urządzeń

1.1 Wszystkie urządzenia

client, run, cookies = get_traccar_client()
 
# GET /api/devices — lista wszystkich urządzeń
devices = traccar_get(run, '/devices')
 
print(f"Liczba urządzeń: {len(devices)}")
for d in devices:
    print(f"  id={d['id']:4d} | uniqueId={d['uniqueId']:20s} | name={d['name']:30s} | status={d.get('status','?')}")
 
client.close()

Przykładowa odpowiedź:

[
  {
    "id": 1,
    "name": "Naczepa TF 1234",
    "uniqueId": "358000001234567",
    "status": "online",
    "disabled": false,
    "lastUpdate": "2026-05-24T10:30:00.000+0000",
    "groupId": 2,
    "phone": "",
    "model": "Teltonika TAT140",
    "contact": "",
    "category": "truck",
    "attributes": {}
  }
]

Częste błędy:

  • 401 Unauthorized — sesja wygasła, uruchom ponownie get_traccar_client()
  • [] pusta lista — zalogowany użytkownik nie ma dostępu do urządzeń; sprawdź uprawnienia w Traccar

1.2 Pojedyncze urządzenie

client, run, cookies = get_traccar_client()
 
device_id = 1  # ← zmień na właściwe ID
 
# GET /api/devices?id=X
device = traccar_get(run, '/devices', {'id': device_id})
# Uwaga: zwraca listę, bierzemy pierwszy element
if isinstance(device, list):
    device = device[0]
 
print(json.dumps(device, indent=2, ensure_ascii=False))
client.close()

1.3 Aktualne pozycje wszystkich urządzeń

client, run, cookies = get_traccar_client()
 
# GET /api/positions — ostatnia znana pozycja każdego urządzenia
positions = traccar_get(run, '/positions')
 
print(f"Pozycje: {len(positions)} urządzeń")
for p in positions:
    lat = p.get('latitude', 0)
    lon = p.get('longitude', 0)
    speed = p.get('speed', 0)
    attrs = p.get('attributes', {})
    ignition = attrs.get('ignition', '?')
    print(f"  deviceId={p['deviceId']:4d} | lat={lat:.5f} lon={lon:.5f} | speed={speed:.1f} km/h | ignition={ignition}")
 
client.close()

Przykładowa odpowiedź (jeden element):

{
  "id": 98765,
  "deviceId": 1,
  "protocol": "teltonika",
  "serverTime": "2026-05-24T10:30:00.000+0000",
  "deviceTime": "2026-05-24T10:29:55.000+0000",
  "fixTime": "2026-05-24T10:29:55.000+0000",
  "outdated": false,
  "valid": true,
  "latitude": 52.23456,
  "longitude": 21.01234,
  "altitude": 105.0,
  "speed": 0.0,
  "course": 270.0,
  "address": "ul. Testowa 1, Warszawa",
  "accuracy": 0.0,
  "attributes": {
    "ignition": true,
    "motion": false,
    "io68": 12345678,
    "io66": 24150,
    "engineHours": 123.45
  }
}

1.4 Pozycja konkretnego urządzenia

client, run, cookies = get_traccar_client()
 
device_id = 1  # ← zmień
 
# GET /api/positions?deviceId=X — ostatnia pozycja urządzenia
positions = traccar_get(run, '/positions', {'deviceId': device_id})
 
if positions:
    p = positions[0]
    print(f"Ostatnia pozycja deviceId={device_id}:")
    print(f"  Czas: {p['fixTime']}")
    print(f"  Lat/Lon: {p['latitude']}, {p['longitude']}")
    print(f"  Prędkość: {p['speed']} km/h")
    print(f"  Atrybuty: {json.dumps(p.get('attributes', {}), indent=4, ensure_ascii=False)}")
 
client.close()

1.5 Historia pozycji (zakres czasowy)

from datetime import datetime, timezone, timedelta
 
client, run, cookies = get_traccar_client()
 
device_id = 1  # ← zmień
# ISO 8601 UTC — ostatnie 24 godziny
now = datetime.now(timezone.utc)
from_dt = (now - timedelta(hours=24)).strftime('%Y-%m-%dT%H:%M:%SZ')
to_dt = now.strftime('%Y-%m-%dT%H:%M:%SZ')
 
# GET /api/positions?deviceId=X&from=...&to=...
positions = traccar_get(run, '/positions', {
    'deviceId': device_id,
    'from': from_dt,
    'to': to_dt
})
 
print(f"Historia deviceId={device_id}: {len(positions)} pozycji w ostatnich 24h")
for p in positions[:5]:  # pokaż pierwsze 5
    print(f"  {p['fixTime']} | lat={p['latitude']:.5f} lon={p['longitude']:.5f} | speed={p['speed']:.1f}")
 
client.close()

Uwaga: Endpoint /api/positions bez parametrów from/to zwraca tylko OSTATNIE pozycje każdego urządzenia. Aby pobrać historię, wymagane są oba parametry from i to.


Sekcja 2: Dodawanie urządzeń

2.1 Tworzenie nowego urządzenia

client, run, cookies = get_traccar_client()
 
# Dostępne kategorie (ikony w Traccar):
CATEGORIES = [
    'arrow', 'default', 'animal', 'bicycle', 'boat', 'bus', 'car', 'crane',
    'helicopter', 'motorcycle', 'offroad', 'person', 'pickup', 'plane',
    'ship', 'tractor', 'train', 'tram', 'trolleybus', 'truck', 'van', 'scooter'
]
 
new_device = {
    "name": "Naczepa Test TF 9999",
    "uniqueId": "358000009999999",    # IMEI lub inny unikalny ID
    "groupId": 0,                     # 0 = brak grupy, lub ID grupy
    "category": "truck",              # ikona na mapie
    "phone": "+48 123 456 789",       # opcjonalne
    "model": "Teltonika TAT140",      # opcjonalne
    "contact": "",                    # opcjonalne
    "attributes": {}                  # dodatkowe atrybuty
}
 
result = traccar_post(run, '/devices', new_device)
print(f"Utworzono urządzenie: id={result.get('id')} name={result.get('name')}")
print(json.dumps(result, indent=2, ensure_ascii=False))
 
client.close()

Przykładowa odpowiedź:

{
  "id": 42,
  "name": "Naczepa Test TF 9999",
  "uniqueId": "358000009999999",
  "status": "unknown",
  "disabled": false,
  "groupId": 0,
  "phone": "+48 123 456 789",
  "model": "Teltonika TAT140",
  "category": "truck",
  "attributes": {}
}

Częste błędy:

  • Duplicate entryuniqueId już istnieje w systemie; użyj innego IMEI
  • 400 Bad Request — brakujące wymagane pole (name lub uniqueId)

Sekcja 3: Edytowanie urządzeń

3.1 Aktualizacja urządzenia (PUT)

Ważne: PUT w Traccar wymaga przesłania CAŁEGO obiektu, nie tylko zmienionych pól.

client, run, cookies = get_traccar_client()
 
device_id = 42  # ← zmień
 
# Krok 1: Pobierz aktualny obiekt
existing = traccar_get(run, '/devices', {'id': device_id})
if isinstance(existing, list):
    existing = existing[0]
 
# Krok 2: Zmodyfikuj pola
existing['name'] = "Naczepa Test TF 9999 — zaktualizowana"
existing['phone'] = "+48 987 654 321"
existing['model'] = "Teltonika FMC650"
 
# Krok 3: PUT z pełnym obiektem
result = traccar_put(run, f'/devices/{device_id}', existing)
print(f"Zaktualizowano: id={result.get('id')} name={result.get('name')}")
 
client.close()

3.2 Aktualizacja wielu pól naraz

client, run, cookies = get_traccar_client()
 
device_id = 42  # ← zmień
 
# Pobierz i zaktualizuj kilka pól
device = traccar_get(run, '/devices', {'id': device_id})
if isinstance(device, list):
    device = device[0]
 
updates = {
    'name': 'Nowa nazwa urządzenia',
    'category': 'van',
    'groupId': 5,
    'phone': '+48 111 222 333',
    'contact': 'Jan Kowalski — kierowca'
}
device.update(updates)
 
result = traccar_put(run, f'/devices/{device_id}', device)
print(f"OK: {result.get('name')} | kategoria={result.get('category')} | grupa={result.get('groupId')}")
 
client.close()

Częste błędy:

  • 404 Not Found — nieprawidłowe device_id
  • Brak zmiany — wysłano GET zamiast PUT, lub brak tokenu sesji

Sekcja 4: Ustawianie ikon / kategorii

4.1 Lista wszystkich dostępnych kategorii

TRACCAR_CATEGORIES = {
    'arrow':      'Strzałka (domyślna)',
    'default':    'Domyślny marker',
    'animal':     'Zwierzę',
    'bicycle':    'Rower',
    'boat':       'Łódź',
    'bus':        'Autobus',
    'car':        'Samochód osobowy',
    'crane':      'Dźwig',
    'helicopter': 'Helikopter',
    'motorcycle': 'Motocykl',
    'offroad':    'Off-road / SUV',
    'person':     'Osoba (pieszo)',
    'pickup':     'Pickup',
    'plane':      'Samolot',
    'ship':       'Statek',
    'tractor':    'Traktor',
    'train':      'Pociąg',
    'tram':       'Tramwaj',
    'trolleybus': 'Trolejbus',
    'truck':      'Ciężarówka / naczepa',
    'van':        'Van / bus dostawczy',
    'scooter':    'Skuter',
}
 
for cat, desc in TRACCAR_CATEGORIES.items():
    print(f"  {cat:12s}{desc}")

4.2 Zmiana kategorii (ikony) urządzenia

client, run, cookies = get_traccar_client()
 
device_id = 42    # ← zmień
new_category = 'truck'  # ← zmień
 
# Pobierz aktualny obiekt i zmień kategorię
device = traccar_get(run, '/devices', {'id': device_id})
if isinstance(device, list):
    device = device[0]
 
old_category = device.get('category', 'default')
device['category'] = new_category
 
result = traccar_put(run, f'/devices/{device_id}', device)
print(f"Zmieniono kategorię: {old_category}{result.get('category')}")
 
client.close()

4.3 Masowa zmiana kategorii dla grupy urządzeń

client, run, cookies = get_traccar_client()
 
group_id = 3         # ← zmień
target_category = 'truck'
 
# Pobierz wszystkie urządzenia w grupie
all_devices = traccar_get(run, '/devices')
group_devices = [d for d in all_devices if d.get('groupId') == group_id]
 
print(f"Znaleziono {len(group_devices)} urządzeń w grupie {group_id}")
for device in group_devices:
    device['category'] = target_category
    result = traccar_put(run, f"/devices/{device['id']}", device)
    print(f"  {result.get('name')}{result.get('category')}")
 
client.close()

Sekcja 5: Grupy urządzeń

5.1 Pobieranie grup

client, run, cookies = get_traccar_client()
 
groups = traccar_get(run, '/groups')
print(f"Grupy ({len(groups)}):")
for g in groups:
    print(f"  id={g['id']:4d} | name={g['name']:30s} | parentGroupId={g.get('groupId', 0)}")
 
client.close()

5.2 Tworzenie nowej grupy

client, run, cookies = get_traccar_client()
 
new_group = {
    "name": "Naczepy — Region Wschód",
    "groupId": 0,        # 0 = brak grupy nadrzędnej (lub ID grupy parent)
    "attributes": {}
}
 
result = traccar_post(run, '/groups', new_group)
print(f"Utworzono grupę: id={result.get('id')} name={result.get('name')}")
 
client.close()

5.3 Edytowanie grupy

client, run, cookies = get_traccar_client()
 
group_id = 5  # ← zmień
 
# Pobierz aktualny obiekt
group = traccar_get(run, f'/groups/{group_id}')  # Uwaga: nie działa bez ID
# Alternatywnie: pobierz wszystkie i filtruj
groups = traccar_get(run, '/groups')
group = next((g for g in groups if g['id'] == group_id), None)
 
if group:
    group['name'] = "Naczepy — Region Wschód (zaktualizowana)"
    result = traccar_put(run, f'/groups/{group_id}', group)
    print(f"Zaktualizowano grupę: {result.get('name')}")
 
client.close()

5.4 Usuwanie grupy

client, run, cookies = get_traccar_client()
 
group_id = 99  # ← zmień — UWAGA: usuwa grupę, urządzenia zostają bez grupy
 
result = traccar_delete(run, f'/groups/{group_id}')
print(f"Usunięto grupę {group_id} (odpowiedź: {result})")
 
client.close()

5.5 Przypisanie urządzenia do grupy

client, run, cookies = get_traccar_client()
 
device_id = 42    # ← zmień
group_id = 5      # ← zmień (0 = brak grupy)
 
# Pobierz urządzenie i zmień groupId
device = traccar_get(run, '/devices', {'id': device_id})
if isinstance(device, list):
    device = device[0]
 
old_group = device.get('groupId', 0)
device['groupId'] = group_id
 
result = traccar_put(run, f'/devices/{device_id}', device)
print(f"Zmieniono grupę: {old_group}{result.get('groupId')}")
 
client.close()

Sekcja 6: Geofences (obszary monitorowania)

6.1 Pobieranie geofences

client, run, cookies = get_traccar_client()
 
geofences = traccar_get(run, '/geofences')
print(f"Geofences ({len(geofences)}):")
for gf in geofences:
    area_short = gf.get('area', '')[:60]
    print(f"  id={gf['id']:4d} | name={gf['name']:30s} | area={area_short}...")
 
client.close()

6.2 Tworzenie geofence — okrąg (CIRCLE)

Format WKT dla okręgu: CIRCLE (lat lon, promień_w_metrach)

client, run, cookies = get_traccar_client()
 
# Okrąg — centrum Warszawy, promień 1 km
new_geofence = {
    "name": "Strefa — Centrum Warszawy",
    "description": "Okrąg 1 km wokół centrum Warszawy",
    "area": "CIRCLE (52.23000 21.01000, 1000)",  # lat lon, promień w metrach
    "calendarId": 0,   # 0 = zawsze aktywne, lub ID kalendarza
    "attributes": {}
}
 
result = traccar_post(run, '/geofences', new_geofence)
print(f"Utworzono geofence: id={result.get('id')} name={result.get('name')}")
 
client.close()

6.3 Tworzenie geofence — wielokąt (POLYGON)

Format WKT dla wielokąta: POLYGON ((lon lat, lon lat, ..., lon lat))
Uwaga: W POLYGON kolejność to lon lat (odwrotnie niż CIRCLE gdzie jest lat lon!)

client, run, cookies = get_traccar_client()
 
# Prostokąt — przykładowy obszar magazynu
new_geofence = {
    "name": "Magazyn — Teren główny",
    "description": "Teren magazynu przy ul. Przemysłowej",
    "area": "POLYGON ((21.0100 52.2300, 21.0120 52.2300, 21.0120 52.2310, 21.0100 52.2310, 21.0100 52.2300))",
    "calendarId": 0,
    "attributes": {}
}
 
result = traccar_post(run, '/geofences', new_geofence)
print(f"Utworzono geofence: id={result.get('id')} name={result.get('name')}")
 
client.close()

6.4 Edytowanie geofence

client, run, cookies = get_traccar_client()
 
geofence_id = 10  # ← zmień
 
geofences = traccar_get(run, '/geofences')
gf = next((g for g in geofences if g['id'] == geofence_id), None)
 
if gf:
    gf['name'] = "Strefa — Centrum Warszawy (zaktualizowana)"
    gf['area'] = "CIRCLE (52.23000 21.01000, 2000)"  # zwiększono promień do 2 km
    result = traccar_put(run, f'/geofences/{geofence_id}', gf)
    print(f"Zaktualizowano: {result.get('name')} | area={result.get('area')}")
 
client.close()

6.5 Usuwanie geofence

client, run, cookies = get_traccar_client()
 
geofence_id = 10  # ← zmień
 
result = traccar_delete(run, f'/geofences/{geofence_id}')
print(f"Usunięto geofence {geofence_id}")
 
client.close()

6.6 Powiązanie geofence z urządzeniem

client, run, cookies = get_traccar_client()
 
device_id = 42
geofence_id = 10
 
# POST /api/permissions — łączy encje w Traccar
permission = {
    "deviceId": device_id,
    "geofenceId": geofence_id
}
 
result = traccar_post(run, '/permissions', permission)
print(f"Powiązano urządzenie {device_id} z geofence {geofence_id}")
 
# Usuń powiązanie (DELETE)
# result = traccar_delete_with_body(run, '/permissions', permission)
 
client.close()

Sekcja 7: Obliczane atrybuty (Computed Attributes)

Computed Attributes to wyrażenia JEXL obliczane na podstawie surowych danych telemetrycznych.

7.1 Pobieranie istniejących atrybutów

client, run, cookies = get_traccar_client()
 
attrs = traccar_get(run, '/attributes/computed')
print(f"Computed Attributes ({len(attrs)}):")
for a in attrs:
    print(f"  id={a['id']:4d} | attribute={a['attribute']:25s} | type={a.get('type','?'):8s} | expr={a['expression']}")
 
client.close()

7.2 Tworzenie computed attribute

client, run, cookies = get_traccar_client()
 
# Przeliczenie napięcia zewnętrznego z mV na V
new_attr = {
    "description": "External Voltage (V)",
    "attribute": "externalVoltage",      # nazwa atrybutu w pozycji
    "expression": "io66 / 1000.0",       # wyrażenie JEXL
    "type": "number"                     # string | number | boolean
}
 
result = traccar_post(run, '/attributes/computed', new_attr)
print(f"Utworzono atrybut: id={result.get('id')} attribute={result.get('attribute')}")
 
client.close()

7.3 Powiązanie computed attribute z urządzeniem

client, run, cookies = get_traccar_client()
 
device_id = 42
attribute_id = 15  # ← ID z POST response
 
permission = {
    "deviceId": device_id,
    "attributeId": attribute_id
}
 
result = traccar_post(run, '/permissions', permission)
print(f"Powiązano urządzenie {device_id} z atrybutem {attribute_id}")
 
client.close()

7.4 Składnia wyrażeń JEXL w Traccar

TypPrzykładOpis
Przeliczenieio66 / 1000.0Podziel wartość przez 1000 (mV → V)
Porównanieio239 == 1Zwróć boolean: czy io239 = 1
Warunekio200 > 0Czy tryb uśpienia aktywny
Konwersjastr(io78)Konwersja na string (np. ID kierowcy)
Działaniaio250 / 1000.0Przeliczenie odległości (m → km)
Division safeio68 > 0 ? io68 / 3600.0 : 0Warunkowe przeliczenie

Typy danych:

  • number — liczba zmiennoprzecinkowa (float)
  • string — tekst
  • booleantrue / false

7.5 Weryfikacja computed attributes po imporcie

client, run, cookies = get_traccar_client()
 
attrs = traccar_get(run, '/attributes/computed')
print(f"\nWszystkie computed attributes ({len(attrs)}):")
print(f"{'ID':>5} {'Attribute':25} {'Type':8} {'Expression'}")
print("-" * 80)
for a in sorted(attrs, key=lambda x: x['attribute']):
    print(f"{a['id']:>5} {a['attribute']:25} {a.get('type','?'):8} {a['expression']}")
 
client.close()

Sekcja 8: Kierowcy (Drivers)

8.1 Pobieranie listy kierowców

client, run, cookies = get_traccar_client()
 
drivers = traccar_get(run, '/drivers')
print(f"Kierowcy ({len(drivers)}):")
for d in drivers:
    print(f"  id={d['id']:4d} | uniqueId={d.get('uniqueId',''):20s} | name={d['name']}")
 
client.close()

8.2 Tworzenie kierowcy

client, run, cookies = get_traccar_client()
 
# uniqueId = wartość io78 (iButton ID) z Teltonika
# Gdy kierowca przyłoży iButton do czytnika, io78 = jego ID → Traccar automatycznie powiązuje
new_driver = {
    "name": "Jan Kowalski",
    "uniqueId": "E0040012345678",   # ← ID iButton (hex, bez myślników)
    "attributes": {
        "phone": "+48 600 700 800"
    }
}
 
result = traccar_post(run, '/drivers', new_driver)
print(f"Utworzono kierowcę: id={result.get('id')} name={result.get('name')} uniqueId={result.get('uniqueId')}")
 
client.close()

8.3 Edytowanie kierowcy

client, run, cookies = get_traccar_client()
 
driver_id = 3  # ← zmień
 
drivers = traccar_get(run, '/drivers')
driver = next((d for d in drivers if d['id'] == driver_id), None)
 
if driver:
    driver['name'] = "Jan Kowalski — kierowca nr 7"
    driver['attributes']['phone'] = "+48 600 700 801"
    result = traccar_put(run, f'/drivers/{driver_id}', driver)
    print(f"Zaktualizowano kierowcę: {result.get('name')}")
 
client.close()

8.4 Usuwanie kierowcy

client, run, cookies = get_traccar_client()
 
driver_id = 3  # ← zmień
 
result = traccar_delete(run, f'/drivers/{driver_id}')
print(f"Usunięto kierowcę {driver_id}")
 
client.close()

8.5 Powiązanie iButton — jak działa automatycznie

Traccar automatycznie przypisuje kierowcę do urządzenia gdy:

  1. Kierowca ma uniqueId = wartość io78 (iButton ID)
  2. Urządzenie przesyła io78 z wartością iButton
  3. Traccar matchuje io78driver.uniqueId → przypisuje do trip

Weryfikacja przez computed attribute:

# Atrybut "driverUniqueId" wyświetla aktualną wartość io78
# Gdy kierowca się zaloguje, w pozycji widać: driverUniqueId = "E0040012345678"

Sekcja 9: Powiadomienia (Notifications)

9.1 Pobieranie powiadomień

client, run, cookies = get_traccar_client()
 
notifications = traccar_get(run, '/notifications')
print(f"Powiadomienia ({len(notifications)}):")
for n in notifications:
    print(f"  id={n['id']:4d} | type={n.get('type','?'):20s} | always={n.get('always',False)} | web={n.get('web',False)}")
 
client.close()

9.2 Lista typów powiadomień

client, run, cookies = get_traccar_client()
 
types = traccar_get(run, '/notifications/types')
print(f"Typy powiadomień ({len(types)}):")
for t in types:
    print(f"  {t.get('type', t)}")
 
client.close()

Standardowe typy Traccar:

TypOpis
deviceOnlineUrządzenie połączyło się
deviceOfflineUrządzenie rozłączyło się
deviceUnknownStatus nieznany (brak danych)
deviceMovingUrządzenie ruszyło
deviceStoppedUrządzenie zatrzymało się
deviceOverspeedPrzekroczenie prędkości
geofenceEnterWjazd do strefy geofence
geofenceExitWyjazd ze strefy geofence
alarmAlarm z urządzenia
ignitionOnZapłon włączony
ignitionOffZapłon wyłączony
maintenancePrzypomnienie o serwisie
textMessageWiadomość tekstowa
driverChangedZmiana kierowcy (iButton)

9.3 Tworzenie powiadomienia

client, run, cookies = get_traccar_client()
 
# Powiadomienie webowe o geofence
new_notification = {
    "type": "geofenceEnter",
    "always": False,         # False = tylko dla powiązanych urządzeń; True = wszystkie urządzenia
    "web": True,             # powiadomienie w web UI
    "mail": False,           # email
    "sms": False,            # SMS
    "calendarId": 0,         # 0 = zawsze aktywne
    "attributes": {}
}
 
result = traccar_post(run, '/notifications', new_notification)
print(f"Utworzono powiadomienie: id={result.get('id')} type={result.get('type')}")
 
client.close()

9.4 Edytowanie powiadomienia

client, run, cookies = get_traccar_client()
 
notif_id = 7  # ← zmień
 
notifications = traccar_get(run, '/notifications')
notif = next((n for n in notifications if n['id'] == notif_id), None)
 
if notif:
    notif['mail'] = True    # włącz email
    notif['web'] = True     # włącz web
    result = traccar_put(run, f'/notifications/{notif_id}', notif)
    print(f"Zaktualizowano: id={result.get('id')} mail={result.get('mail')} web={result.get('web')}")
 
client.close()

9.5 Usuwanie powiadomienia

client, run, cookies = get_traccar_client()
 
notif_id = 7  # ← zmień
 
result = traccar_delete(run, f'/notifications/{notif_id}')
print(f"Usunięto powiadomienie {notif_id}")
 
client.close()

9.6 Powiązanie powiadomienia z urządzeniem

client, run, cookies = get_traccar_client()
 
device_id = 42
notification_id = 7
 
permission = {
    "deviceId": device_id,
    "notificationId": notification_id
}
 
result = traccar_post(run, '/permissions', permission)
print(f"Powiązano urządzenie {device_id} z powiadomieniem {notification_id}")
 
client.close()

Sekcja 10: Strategia przypisywania atrybutów CAN

Computed attributes przypisujemy do urządzeń na podstawie modelu zainstalowanego urządzenia GPS (pole device.model w Traccar). Poniżej opisano obecne podejście i docelowe.


10.1 Obecne podejście — model zainstalowanego urządzenia GPS

Kiedy stosować: teraz, dopóki rejestr floty nie zawiera modelu auta i generacji FMC.

Każde urządzenie GPS wysyła inne zestawy AVL ID (CAN-bus). Dlatego atrybuty dzielimy według modelu:

Model GPSZestawy atrybutówRóżnice vs inne modele
TAT140COMMON + digitalInput1 (io179) + digitalInput2 (io180)Digital inputs na io179/io180
FMC650COMMON + digitalInput1Fmc (io1)Digital input na io1
inneCOMMON (17 atrybutów)Fallback — bez model-specific

COMMON = 17 atrybutów wspólnych: engineHours, totalOdometer, tripOdometer, externalVoltage, batteryVoltage, batteryCurrent, canSpeed, speedLimit, ignition, motion, sleepMode, gsmSignal, pdop, driverUniqueId, iccid, analogInput1, analogInput2.

Przepływ dla nowego urządzenia — obecny:

1. Dodaj urządzenie w Traccar (POST /api/devices, uniqueId = IMEI)
2. Ustaw device.model = "TAT140" lub "FMC650" przez PUT /api/devices/{id}
3. Uruchom skrypt assign-attributes-by-model.py — przypisze właściwy zestaw atrybutów

Skrypt przypisujący atrybuty:

# Dry-run — pokaż co zostanie zrobione
python services/traccar/scripts/assign-attributes-by-model.py --dry-run
 
# Zastosuj
python services/traccar/scripts/assign-attributes-by-model.py

Skrypt matching pattern (w kolejności, pierwsza pasująca wygrywa):

# 1. TAT140 — name zawiera "TAT140" lub "TAT 140"
re.search(r'TAT.?140', device['name'], re.I)  →  TAT140_ATTRIBUTES
 
# 2. FMC650 — name zawiera "FMC650"
re.search(r'FMC.?650', device['name'], re.I)  →  FMC650_ATTRIBUTES
 
# 3. Fallback — wszystkie urządzenia w grupie floty
device['groupId'] != 0COMMON_ATTRIBUTES

Ustawianie pola model przez API (dla nowych urządzeń):

client, run, cookies = get_traccar_client()
 
# Pobierz urządzenie i ustaw model
device = traccar_get(run, '/devices', {'id': device_id})
if isinstance(device, list):
    device = device[0]
 
device['model'] = 'TAT140'   # lub 'FMC650'
result = traccar_put(run, f"/devices/{device['id']}", device)
print(f"Model ustawiony: {result.get('model')}")
 
client.close()

Masowe ustawianie model na podstawie nazwy urządzenia:

import re
 
client, run, cookies = get_traccar_client()
 
MODEL_RULES = [
    ('TAT140', lambda d: bool(re.search(r'TAT.?140', d.get('name', ''), re.I))),
    ('FMC650', lambda d: bool(re.search(r'FMC.?650', d.get('name', ''), re.I))),
]
 
devices = traccar_get(run, '/devices')
for d in devices:
    target_model = next((m for m, fn in MODEL_RULES if fn(d)), None)
    if target_model and d.get('model') != target_model:
        d['model'] = target_model
        result = traccar_put(run, f"/devices/{d['id']}", d)
        print(f"  {d['name']} → model={result.get('model')}")
 
client.close()

10.2 Docelowe podejście — model auta + generacja FMC z rejestru floty

Kiedy stosować: gdy rejestr floty (Supabase fleet_vehicles lub odpowiednik) będzie zawierał model pojazdu i model zainstalowanego urządzenia GPS przed dodaniem do Traccara.

Założenie: różne modele samochodów ciężarowych mają różne magistrale CAN → różne AVL ID → różne computed attributes. Sama generacja FMC (TAT140 vs FMC650 vs przyszłe) determinuje sposób parsowania IO, ale model auta determinuje co jest dostępne na magistrali CAN.

Dane wymagane w rejestrze floty przed rejestracją w Traccar:

PolePrzykładŹródło
vehicle_makeSchmitz, Krone, WieltonRejestracja pojazdu / ręczny wpis
vehicle_modelS.KO 27, Profi Linerj.w.
vehicle_year2021j.w.
fmc_modelTAT140, FMC650Model zamontowanego trackera GPS
fmc_imei861327086001828Z etykiety urządzenia GPS
can_profileschmitz-standard, genericProfil atrybutów CAN (patrz niżej)

Docelowy przepływ rejestracji urządzenia:

Rejestr floty (Supabase)
   └─ vehicle_make, vehicle_model, fmc_model, fmc_imei, can_profile
          │
          ▼
1. Walidacja: can_profile musi być zdefiniowany przed rejestracją
2. POST /api/devices  →  name, uniqueId=fmc_imei, model=fmc_model, category
3. PUT  /api/devices/{id}  →  model=fmc_model (jawne ustawienie)
4. assign-attributes-by-profile.py --device-id=X --profile=can_profile
          │
          ▼
   Traccar: urządzenie z właściwym zestawem CAN attributes

Definicja profili CAN — docelowa struktura:

# services/traccar/scripts/can_profiles.py
# Każdy profil to lista computed attribute names do przypisania.
# Atrybuty muszą istnieć w Traccar (setup-computed-attributes.py).
 
CAN_PROFILES = {
 
    # Profil domyślny — wszystkie modele TAT140 bez specyfiki CAN
    'tat140-generic': {
        'fmc_models': ['TAT140'],
        'attributes': [
            'engineHours', 'totalOdometer', 'tripOdometer',
            'externalVoltage', 'batteryVoltage', 'batteryCurrent',
            'canSpeed', 'speedLimit', 'ignition', 'motion', 'sleepMode',
            'gsmSignal', 'pdop', 'driverUniqueId', 'iccid',
            'analogInput1', 'analogInput2',
            'digitalInput1',   # io179 — TAT140 specyficzne
            'digitalInput2',   # io180 — TAT140 specyficzne
        ],
        'description': 'TAT140 bez dodatkowych danych CAN',
    },
 
    # Profil domyślny — FMC650
    'fmc650-generic': {
        'fmc_models': ['FMC650'],
        'attributes': [
            'engineHours', 'totalOdometer', 'tripOdometer',
            'externalVoltage', 'batteryVoltage', 'batteryCurrent',
            'canSpeed', 'speedLimit', 'ignition', 'motion', 'sleepMode',
            'gsmSignal', 'pdop', 'driverUniqueId', 'iccid',
            'analogInput1', 'analogInput2',
            'digitalInput1Fmc',  # io1 — FMC650 specyficzne
        ],
        'description': 'FMC650 bez dodatkowych danych CAN',
    },
 
    # Profil Schmitz Cargobull — naczepy chłodnicze z CAN Thermo King
    # Do zdefiniowania gdy będą dostępne AVL IDs dla Schmitz + TAT140
    'schmitz-reefer-tat140': {
        'fmc_models': ['TAT140'],
        'attributes': [
            'engineHours', 'totalOdometer', 'tripOdometer',
            'externalVoltage', 'batteryVoltage', 'batteryCurrent',
            'canSpeed', 'speedLimit', 'ignition', 'motion', 'sleepMode',
            'gsmSignal', 'pdop', 'driverUniqueId', 'iccid',
            'digitalInput1', 'digitalInput2',
            # TODO: dodać atrybuty specyficzne dla Schmitz/Thermo King
            # np. 'reeferTemp', 'reeferSetpoint', 'reeferAlarm'
        ],
        'description': 'Schmitz naczepa chłodnicza — TAT140 + CAN Thermo King',
    },
 
    # Profil Krone Profi Liner — standardowa naczepa
    'krone-profi-liner-tat140': {
        'fmc_models': ['TAT140'],
        'attributes': [
            'engineHours', 'totalOdometer', 'tripOdometer',
            'externalVoltage', 'batteryVoltage', 'batteryCurrent',
            'canSpeed', 'speedLimit', 'ignition', 'motion', 'sleepMode',
            'gsmSignal', 'pdop', 'driverUniqueId', 'iccid',
            'digitalInput1', 'digitalInput2',
            # TODO: atrybuty Krone — EBS, loading status etc.
        ],
        'description': 'Krone Profi Liner — TAT140',
    },
}

Skrypt docelowy — assign-attributes-by-profile.py (do zbudowania):

# Użycie:
#   python services/traccar/scripts/assign-attributes-by-profile.py \
#       --device-id 42 \
#       --profile schmitz-reefer-tat140
 
# Lub dla wielu urządzeń naraz (z rejestru Supabase):
#   python services/traccar/scripts/assign-attributes-by-profile.py \
#       --from-registry  # czyta device_id + can_profile z Supabase fleet_vehicles

Wymagania do wdrożenia podejścia docelowego:

  • Tabela fleet_vehicles (lub dev_r_services z rozszerzeniem) zawiera fmc_model, fmc_imei, can_profile
  • Profil CAN zdefiniowany dla każdego modelu auta floty przed rejestracją w Traccar
  • Computed attributes dla profili specyficznych (np. reeferTemp dla Schmitz) dodane przez setup-computed-attributes.py
  • Skrypt assign-attributes-by-profile.py zbudowany i przetestowany
  • Checklist rejestracji urządzenia zaktualizowany: “can_profile wymagany przed POST /api/devices”

10.3 Checklist rejestracji nowego urządzenia GPS w Traccar

□ 1. Sprawdź model GPS (TAT140 / FMC650 / inny) i odczytaj IMEI
□ 2. [docelowo] Wypełnij wpis w rejestrze floty:
        vehicle_make, vehicle_model, fmc_model, fmc_imei, can_profile
□ 3. POST /api/devices:
        name = "{numer_rejestracyjny} {model_GPS}"
        uniqueId = IMEI
        category = "truck" / "trailer" / ...
        model = fmc_model  ("TAT140" / "FMC650")
□ 4. PUT /api/devices/{id} — upewnij się że model jest ustawiony
□ 5. Przypisz do grupy Traccar (PUT device.groupId)
□ 6. Uruchom assign-attributes-by-model.py (teraz) lub assign-attributes-by-profile.py (docelowo)
□ 7. Weryfikacja: GET /api/attributes/computed?deviceId={id} — sprawdź liczbę przypisanych atrybutów
□ 8. Sprawdź pierwszą pozycję z urządzenia: GET /api/positions?deviceId={id}
        — czy atrybuty CAN są obecne i mają sensowne wartości?

Serwery (Traccar config)

# GET /api/server — konfiguracja serwera
server = traccar_get(run, '/server')
print(f"Traccar server: version={server.get('version')} map={server.get('map')}")

Zdarzenia (Events)

from datetime import datetime, timezone, timedelta
 
now = datetime.now(timezone.utc)
from_dt = (now - timedelta(hours=24)).strftime('%Y-%m-%dT%H:%M:%SZ')
to_dt = now.strftime('%Y-%m-%dT%H:%M:%SZ')
 
# GET /api/events?deviceId=X&from=...&to=... — zdarzenia urządzenia
events = traccar_get(run, '/events', {
    'deviceId': 1,
    'from': from_dt,
    'to': to_dt
})
print(f"Zdarzenia: {len(events)}")
for e in events[:10]:
    print(f"  {e.get('type'):20s} | {e.get('eventTime')} | deviceId={e.get('deviceId')}")

Raporty — trasy (Trips)

from datetime import datetime, timezone, timedelta
 
now = datetime.now(timezone.utc)
from_dt = (now - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ')
to_dt = now.strftime('%Y-%m-%dT%H:%M:%SZ')
 
# GET /api/reports/trips — raporty tras
trips = traccar_get(run, '/reports/trips', {
    'deviceId': 1,
    'from': from_dt,
    'to': to_dt
})
print(f"Trasy z ostatnich 7 dni: {len(trips)}")
for t in trips[:5]:
    dist_km = t.get('distance', 0) / 1000.0
    print(f"  start={t.get('startTime')} | dystans={dist_km:.1f} km | avg_speed={t.get('averageSpeed', 0):.1f}")

Troubleshooting

ProblemPrzyczynaRozwiązanie
401 UnauthorizedSesja wygasłaUruchom ponownie get_traccar_client()
404 Not FoundZłe ID zasobuPobierz aktualną listę i sprawdź ID
400 Bad RequestBrakujące pola lub złe typySprawdź wymagane pola w odpowiedzi serwera
Duplicate entryuniqueId już istniejeZmień uniqueId lub usuń istniejące urządzenie
Puste []Brak uprawnień lub brak danychSprawdź uprawnienia użytkownika w Traccar admin
Cookie expiredDomyślny timeout sesji TraccarZaloguj ponownie (sesja Traccar domyślnie trwa 24h)
Connection refusedKontener traccar zatrzymanyssh root@217.154.82.162 "docker start traccar"

Linki