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 204Sekcja 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 ponownieget_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 entry—uniqueIdjuż istnieje w systemie; użyj innego IMEI400 Bad Request— brakujące wymagane pole (namelubuniqueId)
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łowedevice_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
| Typ | Przykład | Opis |
|---|---|---|
| Przeliczenie | io66 / 1000.0 | Podziel wartość przez 1000 (mV → V) |
| Porównanie | io239 == 1 | Zwróć boolean: czy io239 = 1 |
| Warunek | io200 > 0 | Czy tryb uśpienia aktywny |
| Konwersja | str(io78) | Konwersja na string (np. ID kierowcy) |
| Działania | io250 / 1000.0 | Przeliczenie odległości (m → km) |
| Division safe | io68 > 0 ? io68 / 3600.0 : 0 | Warunkowe przeliczenie |
Typy danych:
number— liczba zmiennoprzecinkowa (float)string— tekstboolean—true/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:
- Kierowca ma
uniqueId= wartośćio78(iButton ID) - Urządzenie przesyła
io78z wartością iButton - Traccar matchuje
io78→driver.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:
| Typ | Opis |
|---|---|
deviceOnline | Urządzenie połączyło się |
deviceOffline | Urządzenie rozłączyło się |
deviceUnknown | Status nieznany (brak danych) |
deviceMoving | Urządzenie ruszyło |
deviceStopped | Urządzenie zatrzymało się |
deviceOverspeed | Przekroczenie prędkości |
geofenceEnter | Wjazd do strefy geofence |
geofenceExit | Wyjazd ze strefy geofence |
alarm | Alarm z urządzenia |
ignitionOn | Zapłon włączony |
ignitionOff | Zapłon wyłączony |
maintenance | Przypomnienie o serwisie |
textMessage | Wiadomość tekstowa |
driverChanged | Zmiana 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 GPS | Zestawy atrybutów | Różnice vs inne modele |
|---|---|---|
TAT140 | COMMON + digitalInput1 (io179) + digitalInput2 (io180) | Digital inputs na io179/io180 |
FMC650 | COMMON + digitalInput1Fmc (io1) | Digital input na io1 |
| inne | COMMON (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.pySkrypt 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'] != 0 → COMMON_ATTRIBUTESUstawianie 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:
| Pole | Przykład | Źródło |
|---|---|---|
vehicle_make | Schmitz, Krone, Wielton | Rejestracja pojazdu / ręczny wpis |
vehicle_model | S.KO 27, Profi Liner | j.w. |
vehicle_year | 2021 | j.w. |
fmc_model | TAT140, FMC650 | Model zamontowanego trackera GPS |
fmc_imei | 861327086001828 | Z etykiety urządzenia GPS |
can_profile | schmitz-standard, generic | Profil 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_vehiclesWymagania do wdrożenia podejścia docelowego:
- Tabela
fleet_vehicles(lubdev_r_servicesz rozszerzeniem) zawierafmc_model,fmc_imei,can_profile - Profil CAN zdefiniowany dla każdego modelu auta floty przed rejestracją w Traccar
- Computed attributes dla profili specyficznych (np.
reeferTempdla Schmitz) dodane przezsetup-computed-attributes.py - Skrypt
assign-attributes-by-profile.pyzbudowany 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
| Problem | Przyczyna | Rozwiązanie |
|---|---|---|
401 Unauthorized | Sesja wygasła | Uruchom ponownie get_traccar_client() |
404 Not Found | Złe ID zasobu | Pobierz aktualną listę i sprawdź ID |
400 Bad Request | Brakujące pola lub złe typy | Sprawdź wymagane pola w odpowiedzi serwera |
Duplicate entry | uniqueId już istnieje | Zmień uniqueId lub usuń istniejące urządzenie |
Puste [] | Brak uprawnień lub brak danych | Sprawdź uprawnienia użytkownika w Traccar admin |
| Cookie expired | Domyślny timeout sesji Traccar | Zaloguj ponownie (sesja Traccar domyślnie trwa 24h) |
Connection refused | Kontener traccar zatrzymany | ssh root@217.154.82.162 "docker start traccar" |
Linki
- Traccar API Reference
- Traccar API Swagger — lokalnie:
http://localhost:8082/api/swagger - JEXL Expression Language
- Teltonika TAT140 AVL IDs
- Teltonika FMC650 AVL IDs