GPS Tracker API - Ortungsdaten in deine Software integrieren
Mit der my-GPS GPS Tracker API bindest du die Ortungsdaten deiner Fahrzeuge, Maschinen und anderen GPS-Tracker in deine eigene Software ein. Deine Anwendung ruft die zuletzt übermittelten Daten per HTTPS ab und erhält eine strukturierte XML-Antwort.
Was kannst du mit der GPS Tracker API machen?
Nutze deine GPS-Daten dort, wo du sie im Arbeitsalltag brauchst: in deiner Fuhrpark-Software, einer Warenwirtschaft, einem eigenen Dashboard oder einer individuellen Kundenanwendung. Die Geräte-ID hilft dir, die zurückgegebenen Werte dem richtigen Fahrzeug oder Objekt zuzuordnen.
Zeige die zuletzt gemeldeten GPS-Koordinaten und den zugehörigen Datenzeitpunkt in deiner Anwendung an.
Verknüpfe Geräte-ID, Namen und Kennzeichen mit den Fahrzeugen oder Maschinen in deinem System.
Verarbeite die zuletzt gemeldete Geschwindigkeit und den Akkustand, sofern dein Tracker einen Akkuwert liefert.
Die Aktion live liefert den letzten übertragenen Datenstand pro Gerät. Der GPS-Zeitstempel zeigt dir, wie aktuell die Position ist. Das Übertragungsintervall und die Verbindung deines Trackers bestimmen die Aktualität.
So funktioniert der API-Abruf
Deine Anwendung sendet einen GET-Aufruf an den API-Endpunkt. Den API-Key übergibst du zusammen mit der Version und der gewünschten Aktion in der URL. Für die aktuelle hier dokumentierte Schnittstelle lauten die Parameter api_version=7-2-26 und api_action=live.
https://gps.my-gps.org/api/api_v2.php?api_key=DEIN_API_KEY&api_version=7-2-26&api_action=liveErstelle deinen API-Key im my-GPS-Portal und hinterlege ihn in der Serverkonfiguration deiner Anwendung. Im Portal kannst du bis zu zwei Keys verwalten, anzeigen, pausieren, wieder aktivieren und löschen.
Rufe die Daten standardmäßig höchstens einmal alle 60 Sekunden ab. Verwende HTTPS und führe den Abruf auf deinem Server aus. Halte den Key aus öffentlich ausgeliefertem JavaScript und aus Anfrage-Logs heraus.
Aufbau der XML-Antwort
Unter findest du den API-Status und die Geräteanzahl. enthält für jedes zurückgegebene Gerät einen -Eintrag. Das folgende Beispiel nutzt Demowerte im Aufbau deiner API-Antwort.
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<status_description>OK</status_description>
<item_count>1</item_count>
<items>
<item>
<id>0123456789abcdef0123456789abcdef</id>
<internal_name><![CDATA[Transporter 1]]></internal_name>
<license_plate><![CDATA[OS-MG 123]]></license_plate>
<utc_timestamp_gps>1788869676</utc_timestamp_gps>
<device_timezone>2</device_timezone>
<longitude>13.4049540</longitude>
<latitude>52.5200080</latitude>
<valid>1</valid>
<battery>-1</battery>
<speed>2</speed>
</item>
</items>
</response>
Deine Sprache. Dein API-Abruf.
Wähle deine Programmiersprache und kopiere das passende Beispiel. Die Beispiele verwenden deinen Key aus MGPS_API_KEY und geben die XML-Antwort aus.
.NET 6+ · HttpClient
using System;
using System.Net.Http;
using System.Threading;
var key = Environment.GetEnvironmentVariable("MGPS_API_KEY")
?? throw new InvalidOperationException("MGPS_API_KEY fehlt.");
var url = "https://gps.my-gps.org/api/api_v2.php?api_key="
+ Uri.EscapeDataString(key) + "&api_version=7-2-26&api_action=live";
using var client = new HttpClient(new HttpClientHandler {
AllowAutoRedirect = false
});
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var response = await client.GetAsync(url, timeout.Token);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
Terminal · API-Key aus der Umgebungsvariable
curl --silent --show-error --fail --max-time 30 \
--get "https://gps.my-gps.org/api/api_v2.php" \
--data-urlencode "api_key=${MGPS_API_KEY:?MGPS_API_KEY fehlt}" \
--data-urlencode "api_version=7-2-26" \
--data-urlencode "api_action=live"
# Optional: -4 erzwingt eine IPv4-Verbindung beim direkten Aufruf.
Java 11+ · HttpClient
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class Live {
public static void main(String[] args) throws Exception {
String key = System.getenv("MGPS_API_KEY");
if (key == null || key.isBlank()) throw new IllegalStateException("MGPS_API_KEY fehlt.");
String url = "https://gps.my-gps.org/api/api_v2.php?api_key="
+ URLEncoder.encode(key, StandardCharsets.UTF_8)
+ "&api_version=7-2-26&api_action=live";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.timeout(Duration.ofSeconds(30)).header("Accept", "application/xml").GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IllegalStateException("HTTP " + response.statusCode());
System.out.println(response.body());
}
}
Browser · Abruf über deinen eigenen Backend-Endpunkt
// Dein Backend ruft my-GPS auf; der API-Key bleibt ausschließlich dort.
// Route an deine Anwendung anpassen. Backend muss den Benutzer authentifizieren.
async function loadLivePositions() {
const response = await fetch('/my-gps-live.php', {
headers: { Accept: 'application/xml' },
credentials: 'same-origin',
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const text = await response.text();
const xml = new DOMParser().parseFromString(text, 'application/xml');
if (xml.querySelector('parsererror')) throw new Error('Ungültiges XML');
const status = xml.querySelector('response > status_code')?.textContent;
if (status !== '200') throw new Error(`API-Status ${status}`);
return xml;
}
loadLivePositions().then(xml => console.log(xml)).catch(console.error);
Kotlin/JVM · JDK 11+ · HttpClient
import java.net.URI
import java.net.URLEncoder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets
import java.time.Duration
fun main() {
val key = System.getenv("MGPS_API_KEY") ?: error("MGPS_API_KEY fehlt.")
val url = "https://gps.my-gps.org/api/api_v2.php?api_key=" +
URLEncoder.encode(key, StandardCharsets.UTF_8) +
"&api_version=7-2-26&api_action=live"
val client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build()
val request = HttpRequest.newBuilder(URI.create(url))
.timeout(Duration.ofSeconds(30)).header("Accept", "application/xml").GET().build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
check(response.statusCode() == 200) { "HTTP ${response.statusCode()}" }
println(response.body())
}
Node.js 20+ · natives fetch · serverseitig
const key = process.env.MGPS_API_KEY;
if (!key) throw new Error('MGPS_API_KEY fehlt.');
const url = new URL('https://gps.my-gps.org/api/api_v2.php');
url.search = new URLSearchParams({
api_key: key, api_version: '7-2-26', api_action: 'live'
}).toString();
const response = await fetch(url, {
headers: { Accept: 'application/xml' },
redirect: 'error',
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.text());
PHP 5.6+ · cURL und SimpleXML · serverseitig
<?php
$apiKey = getenv('MGPS_API_KEY');
if (!$apiKey) { throw new RuntimeException('MGPS_API_KEY fehlt.'); }
$url = 'https://gps.my-gps.org/api/api_v2.php?' . http_build_query(array(
'api_key' => $apiKey, 'api_version' => '7-2-26', 'api_action' => 'live'
), '', '&');
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2
));
// Optional bei einer ausschließlich für IPv4 hinterlegten Freigabe:
// curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
$body = curl_exec($ch);
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
curl_close($ch);
if ($body === false || $http !== 200) {
throw new RuntimeException('Abruf fehlgeschlagen: HTTP ' . $http . ', cURL ' . $errno);
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NONET);
if ($xml === false || (string) $xml->status_code !== '200') {
throw new RuntimeException('Die API hat keine erfolgreiche XML-Antwort geliefert.');
}
header('Content-Type: application/xml; charset=UTF-8');
header('Cache-Control: no-store');
echo $body;
PowerShell 7+ · Invoke-WebRequest
$key = $env:MGPS_API_KEY
if ([string]::IsNullOrWhiteSpace($key)) { throw 'MGPS_API_KEY fehlt.' }
$uri = 'https://gps.my-gps.org/api/api_v2.php?api_key=' +
[Uri]::EscapeDataString($key) + '&api_version=7-2-26&api_action=live'
$response = Invoke-WebRequest -Uri $uri -Method Get -TimeoutSec 30 -MaximumRedirection 0
[xml]$xml = $response.Content
if ($xml.response.status_code -ne '200') {
throw ('API-Status ' + $xml.response.status_code)
}
$response.Content
Python 3 · urllib · keine Zusatzpakete
import os
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
key = os.environ['MGPS_API_KEY']
query = urllib.parse.urlencode({
'api_key': key, 'api_version': '7-2-26', 'api_action': 'live'
})
request = urllib.request.Request(
'https://gps.my-gps.org/api/api_v2.php?' + query,
headers={'Accept': 'application/xml'}
)
with urllib.request.urlopen(request, timeout=30) as response:
body = response.read()
root = ET.fromstring(body)
if root.findtext('status_code') != '200':
raise RuntimeError('API-Status ' + str(root.findtext('status_code')))
print(body.decode('utf-8'))
Ruby · Net::HTTP und REXML (ggf. gem install rexml)
require 'net/http'
require 'uri'
require 'openssl'
require 'rexml/document'
key = ENV.fetch('MGPS_API_KEY')
uri = URI('https://gps.my-gps.org/api/api_v2.php')
uri.query = URI.encode_www_form(api_key: key, api_version: '7-2-26', api_action: 'live')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.open_timeout = 10
http.read_timeout = 30
request = Net::HTTP::Get.new(uri)
request['Accept'] = 'application/xml'
response = http.request(request)
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
xml = REXML::Document.new(response.body)
status = xml.elements['response/status_code']&.text
raise "API-Status #{status}" unless status == '200'
puts response.body
Swift 5.5+ · URLSession · Server/CLI mit Umgebungsvariable
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
@main
struct Live {
static func main() async throws {
guard let key = ProcessInfo.processInfo.environment["MGPS_API_KEY"], !key.isEmpty else {
throw NSError(domain: "myGPS", code: 1,
userInfo: [NSLocalizedDescriptionKey: "MGPS_API_KEY fehlt."])
}
var url = URLComponents(string: "https://gps.my-gps.org/api/api_v2.php")!
url.queryItems = [URLQueryItem(name: "api_key", value: key),
URLQueryItem(name: "api_version", value: "7-2-26"),
URLQueryItem(name: "api_action", value: "live")]
var request = URLRequest(url: url.url!)
request.timeoutInterval = 30
request.setValue("application/xml", forHTTPHeaderField: "Accept")
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw NSError(domain: "myGPS", code: 2,
userInfo: [NSLocalizedDescriptionKey: "HTTP-Aufruf fehlgeschlagen."])
}
print(String(decoding: data, as: UTF8.self))
}
}
Prüfe in deiner Anwendung zusätzlich zum HTTP-Status immer status_code im XML. Die Kurzbeispiele für C#, cURL, Java, Kotlin, Node.js und Swift zeigen zunächst den Abruf und die XML-Ausgabe. Browser-JavaScript ruft dein eigenes, authentifiziertes Backend auf - der my-GPS-Key gehört nicht in öffentliches JavaScript oder in eine ausgelieferte App.
Bei cURL kannst du optional -4 setzen, wenn nur deine IPv4 freigegeben ist. Für IPv6 hinterlegst du die tatsächlich verwendete IPv6-Adresse beim Key. Die Beispiele starten keine wiederholten Abrufe; plane dafür mindestens 60 Sekunden Abstand.
Welche Werte erhält deine Anwendung?
| XML-Feld | Inhalt | Bedeutung |
|---|---|---|
id | Geräte-ID | Eindeutige Kennung zur Zuordnung des Geräts in deiner Anwendung. |
internal_name | Gerätename | Der im my-GPS-Portal hinterlegte Name deines Fahrzeugs oder Objekts. |
license_plate | Kennzeichen | Das hinterlegte Kfz-Kennzeichen. -1 bedeutet: Es wurde noch kein Kennzeichen eingetragen. |
utc_timestamp_gps | GPS-Zeitpunkt | UNIX-Zeitstempel in Sekunden, bezogen auf UTC. 0 bedeutet: Das Gerät war bisher noch nicht verbunden. |
device_timezone | UTC-Offset | Gerätezeitzone als Stundenoffset. Für Deutschland: 1 im Winter, 2 im Sommer; das System stellt dies automatisch um. |
longitude | Längengrad | Längengrad der zuletzt übertragenen Position. |
latitude | Breitengrad | Breitengrad der zuletzt übertragenen Position. |
valid | Positionsstatus | 1: Koordinaten sind gültig. 0: Es sind keine gültigen Koordinaten vorhanden; longitude und latitude stehen dann auf 0. |
battery | Akkustand | Akkuladung in Prozent, 0 bis 100. -1 bedeutet: Das Gerät liefert keinen Akkuwert. |
speed | Geschwindigkeit | Zuletzt übertragene Geschwindigkeit in km/h, auf ganze Zahlen gerundet. |
Speichere GPS-Zeitpunkte in UTC. Für die Anzeige in Deutschland kannst du anschließend die Zeitzone Europe/Berlin verwenden. Fehlende Werte wie Akku -1 oder Zeitstempel 0 solltest du als?nicht verfügbar? behandeln.
Du bestimmst, welche Daten dein Key freigibt
Standardmäßig sind alle eigenen Geräte freigegeben. Im Portal kannst du für jeden Key eine feste Auswahl treffen. Neue Geräte müssen bei einer festen Auswahl separat freigegeben werden.
Hinterlege die öffentlichen IP-Adressen deiner Anwendung. Bei aktiver Liste sind nur diese Adressen erlaubt. Eine leere Liste hebt die IP-Einschränkung auf.
Im Portal siehst du je Key die Gesamtzähler und die letzten protokollierten Aufrufe mit Client-IP und Ergebnis.
Das Abrufintervall beträgt 60 Sekunden pro Benutzer, gemeinsam für beide Keys. 5 Sekunden Toleranz gleichen kleine Zeitabweichungen aus. Plane regulär mit 60 Sekunden. Bei zu frühem Abruf liefert die API XML-Code 429, retry_after und den Header Retry-After mit der verbleibenden Wartezeit in Sekunden. Abgelehnte Aufrufe verlängern die Wartezeit nicht.
Bei einem Aufruf über IPv4 wird die öffentliche IPv4 geprüft, bei IPv6 die IPv6. Eine IPv4-Freigabe erlaubt keinen IPv6-Aufruf. Hinter Cloudflare verwenden wir die echte Client-IP; die künstliche Pseudo-IPv4 wird nicht zur Freigabe genutzt.
Statuscodes richtig auswerten
Prüfe den HTTP-Status und zusätzlich das XML-Feld status_code. Die API kann auch bei HTTP 200 einen Fehler im XML melden. status_description beschreibt den API-Status; item_count enthält die Anzahl der Geräte.
| XML-Status | Bedeutung | Nächster Schritt |
|---|---|---|
200 | OK | Antwort verarbeiten. |
404 | API-Key ungültig | Key und Kundenstatus prüfen. Pausierte oder gelöschte Keys sind ungültig. |
403 | Client-IP nicht freigegeben | Die tatsächlich verwendete IPv4 oder IPv6 beim Key freigeben. |
429 | Zu viele Anfragen | Nach retry_after Sekunden erneut versuchen; regulär mindestens 60 Sekunden Abstand pro Benutzer. |
500 | HTTPS erforderlich oder interner Fehler | HTTPS verwenden und bei wiederholtem Fehler den Support kontaktieren. |
PHP-cURL-Beispiel mit MySQL-Speicherung
Das Beispiel für PHP 8.5 ruft die XML-Antwort mit cURL ab, prüft beide Statuswerte und verarbeitet die Geräteeinträge. PDO speichert pro Gerät einen Datensatz in MySQL. Weitere Abrufe aktualisieren denselben Datensatz.
Du benötigst die PHP-Erweiterungen cURL, SimpleXML und PDO MySQL. Hinterlege API-Key und Datenbankzugang über Umgebungsvariablen. Führe das Skript mit dem Taskplaner oder einem Cronjob höchstens einmal pro Minute aus.
Vollständiges PHP-Beispiel anzeigen
<?php
declare(strict_types=1);
/** PHP 8.5; benötigt ext-curl, ext-simplexml und ext-pdo_mysql. */
final class MyGpsImportException extends RuntimeException {}
function fetchMyGpsLiveXml(#[SensitiveParameter] string $apiKey): string
{
if ($apiKey === '' || preg_match('/\s/', $apiKey)) {
throw new MyGpsImportException('Bitte einen gültigen API-Key hinterlegen.');
}
$url = 'https://gps.my-gps.org/api/api_v2.php?' . http_build_query([
'api_key' => $apiKey,
'api_version' => '7-2-26',
'api_action' => 'live',
], '', '&', PHP_QUERY_RFC3986);
$curl = curl_init($url);
$body = '';
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ['Accept: application/xml'],
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_TIMEOUT => 20,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'my-GPS-API-Example/1.0',
CURLOPT_WRITEFUNCTION => static function ($handle, string $chunk) use (&$body): int {
if (strlen($body) + strlen($chunk) > 4 * 1024 * 1024) {
return 0; // Unerwartet grosse Antworten abbrechen.
}
$body .= $chunk;
return strlen($chunk);
},
]);
if (defined('CURLOPT_PROTOCOLS_STR')) {
curl_setopt($curl, CURLOPT_PROTOCOLS_STR, 'https');
} else {
curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
}
$ok = curl_exec($curl);
$curlCode = curl_errno($curl);
$httpCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
// CurlHandle wird freigegeben, wenn die Funktion endet.
if ($ok === false) {
// curl_error() und die URL nicht ausgeben: Sie koennten den Key enthalten.
throw new MyGpsImportException('Abruf fehlgeschlagen (cURL-Code ' . $curlCode . ').');
}
if ($httpCode !== 200) {
throw new MyGpsImportException('Die API antwortet mit HTTP-Status ' . $httpCode . '.');
}
return $body;
}
function myGpsField(SimpleXMLElement $node, string $field): string
{
if (count($node->{$field}) !== 1) {
throw new MyGpsImportException('Die XML-Antwort enthält ein fehlendes oder mehrfaches Feld: ' . $field);
}
return (string) $node->{$field};
}
function myGpsInteger(string $value, int $minimum, int $maximum): int
{
$parsed = filter_var(trim($value), FILTER_VALIDATE_INT, [
'options' => ['min_range' => $minimum, 'max_range' => $maximum],
]);
if ($parsed === false) {
throw new MyGpsImportException('Die API enthält einen ungültigen Zahlenwert.');
}
return $parsed;
}
function myGpsCoordinate(string $value, float $minimum, float $maximum): string
{
$value = trim($value);
if (!preg_match('/\A-?\d+(?:\.\d+)?\z/D', $value)
|| (float) $value < $minimum || (float) $value > $maximum) {
throw new MyGpsImportException('Die API enthält eine ungültige Koordinate.');
}
return number_format((float) $value, 7, '.', '');
}
function myGpsText(string $value, int $maximum): string
{
if (!preg_match('//u', $value) || preg_match_all('/./us', $value) > $maximum) {
throw new MyGpsImportException('Ein Textfeld der API ist ungültig oder zu lang.');
}
return $value;
}
function parseMyGpsLiveXml(string $body): array
{
// Keine DTD/Entities verarbeiten; NONET allein schuetzt nicht vor lokalen Dateien.
if ($body === '' || strlen($body) > 4 * 1024 * 1024
|| preg_match('/<!\s*(?:DOCTYPE|ENTITY)\b/i', $body)) {
throw new MyGpsImportException('Die API hat keine gültige XML-Antwort geliefert.');
}
$previous = libxml_use_internal_errors(true);
try {
$xml = simplexml_load_string($body, SimpleXMLElement::class, LIBXML_NONET | LIBXML_NOCDATA);
} finally {
libxml_clear_errors();
libxml_use_internal_errors($previous);
}
if ($xml === false || $xml->getName() !== 'response') {
throw new MyGpsImportException('Die API hat keine gültige XML-Antwort geliefert.');
}
$status = myGpsInteger(myGpsField($xml, 'status_code'), 100, 599);
if ($status !== 200) {
$messages = [
404 => 'Der API-Key ist ungültig, pausiert oder gelöscht.',
429 => 'Zu viele Anfragen. Den nächsten Abruf frühestens nach 60 Sekunden ausführen.',
503 => 'Die API ist vorübergehend nicht verfügbar. Später erneut versuchen.',
500 => 'Die API verlangt eine sichere HTTPS-Verbindung.',
];
throw new MyGpsImportException($messages[$status] ?? 'Die API meldet einen Fehler.');
}
myGpsField($xml, 'status_description');
$count = myGpsInteger(myGpsField($xml, 'item_count'), 0, 100000);
if (count($xml->items) !== 1 || count($xml->items->item) !== $count) {
throw new MyGpsImportException('Die Geräteanzahl passt nicht zur XML-Antwort.');
}
$rows = [];
$seen = [];
foreach ($xml->items->item as $item) {
$id = trim(myGpsField($item, 'id'));
if (!preg_match('/\A[a-zA-Z0-9_-]{1,64}\z/D', $id) || isset($seen[$id])) {
throw new MyGpsImportException('Eine Geräte-ID ist ungültig oder mehrfach vorhanden.');
}
$seen[$id] = true;
$valid = myGpsInteger(myGpsField($item, 'valid'), 0, 1);
$timestamp = myGpsInteger(myGpsField($item, 'utc_timestamp_gps'), 0, PHP_INT_MAX);
// MySQL DATETIME unterstuetzt Jahre bis 9999.
$utcDate = $timestamp === 0 ? null : gmdate('Y-m-d H:i:s', $timestamp);
if ($utcDate !== null && strlen($utcDate) !== 19) {
throw new MyGpsImportException('Der GPS-Zeitstempel liegt außerhalb des unterstützten Bereichs.');
}
$plate = trim(myGpsField($item, 'license_plate'));
$battery = myGpsInteger(myGpsField($item, 'battery'), -1, 100);
$longitude = myGpsCoordinate(myGpsField($item, 'longitude'), -180, 180);
$latitude = myGpsCoordinate(myGpsField($item, 'latitude'), -90, 90);
$rows[] = [
'device_id' => $id,
'internal_name' => myGpsText(myGpsField($item, 'internal_name'), 255),
'license_plate' => $plate === '-1' || $plate === '' ? null : myGpsText($plate, 64),
'utc_timestamp_gps' => $timestamp === 0 ? null : $timestamp,
'gps_recorded_at_utc' => $utcDate,
'device_timezone' => myGpsInteger(myGpsField($item, 'device_timezone'), -14, 14),
'longitude' => $valid === 1 ? $longitude : null,
'latitude' => $valid === 1 ? $latitude : null,
'valid' => $valid,
'battery' => $battery === -1 ? null : $battery,
'speed' => myGpsInteger(myGpsField($item, 'speed'), 0, 65535),
];
}
return $rows;
}
function saveMyGpsLiveRows(PDO $pdo, array $rows): int
{
$sql = 'INSERT INTO gps_live_positions
(device_id, internal_name, license_plate, utc_timestamp_gps, gps_recorded_at_utc,
device_timezone, longitude, latitude, valid, battery, speed, fetched_at_utc)
VALUES (:device_id, :internal_name, :license_plate, :utc_timestamp_gps, :gps_recorded_at_utc,
:device_timezone, :longitude, :latitude, :valid, :battery, :speed, :fetched_at_utc)
ON DUPLICATE KEY UPDATE
internal_name=VALUES(internal_name), license_plate=VALUES(license_plate),
utc_timestamp_gps=VALUES(utc_timestamp_gps), gps_recorded_at_utc=VALUES(gps_recorded_at_utc),
device_timezone=VALUES(device_timezone), longitude=VALUES(longitude), latitude=VALUES(latitude),
valid=VALUES(valid), battery=VALUES(battery), speed=VALUES(speed), fetched_at_utc=VALUES(fetched_at_utc)';
$statement = $pdo->prepare($sql);
$fetchedAt = gmdate('Y-m-d H:i:s');
$pdo->beginTransaction();
try {
foreach ($rows as $row) {
$row['fetched_at_utc'] = $fetchedAt;
$statement->execute($row);
}
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) { $pdo->rollBack(); }
throw $e;
}
return count($rows);
}
function runMyGpsImport(): int
{
if (PHP_SAPI !== 'cli') { http_response_code(404); return 1; }
$apiKey = trim((string) getenv('MGPS_API_KEY'));
$dsn = (string) getenv('MGPS_DB_DSN');
$dbUser = (string) getenv('MGPS_DB_USER');
$dbPassword = (string) getenv('MGPS_DB_PASSWORD');
if ($apiKey === '' || strpos($dsn, 'mysql:') !== 0 || $dbUser === '') {
fwrite(STDERR, "Bitte MGPS_API_KEY, MGPS_DB_DSN, MGPS_DB_USER und MGPS_DB_PASSWORD konfigurieren.\n");
return 1;
}
// Verhindert ueberlappende Jobs fuer dieselbe Datenbank auf diesem Rechner.
$lockFile = sys_get_temp_dir() . '/my-gps-import-' . hash('sha256', $dsn . '|' . $dbUser) . '.lock';
$lock = fopen($lockFile, 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
fwrite(STDERR, "Ein Import läuft bereits oder die Lockdatei ist nicht verfügbar.\n");
return 1;
}
try {
$rows = parseMyGpsLiveXml(fetchMyGpsLiveXml($apiKey));
$pdo = new PDO($dsn, $dbUser, $dbPassword, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$pdo->exec("SET time_zone = '+00:00'");
$pdo->exec("SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'");
$number = saveMyGpsLiveRows($pdo, $rows);
fwrite(STDOUT, $number . " GPS-Datensätze aktualisiert.\n");
return 0;
} catch (MyGpsImportException $e) {
fwrite(STDERR, $e->getMessage() . "\n");
return 1;
} catch (Throwable $e) {
// Keine Anfrage-URL, Zugangsdaten oder Payload in die Ausgabe schreiben.
fwrite(STDERR, "Der Import konnte nicht gespeichert werden. Bitte Datenbank und PHP-Erweiterungen prüfen.\n");
return 1;
} finally {
flock($lock, LOCK_UN);
fclose($lock);
}
}
if (isset($_SERVER['SCRIPT_FILENAME']) && realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__) {
exit(runMyGpsImport());
}
MySQL-Tabelle anzeigen
-- Eine Zeile pro Geraet; der PHP-Import aktualisiert den letzten Datenstand.
-- Kompatibel mit MySQL 5.6 und neueren MySQL-Versionen.
-- Sonderwerte -1 / Zeitstempel 0 / ungueltige Koordinaten werden zu NULL.
CREATE TABLE IF NOT EXISTS `gps_live_positions` (
`device_id` VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`internal_name` VARCHAR(255) NOT NULL,
`license_plate` VARCHAR(64) NULL DEFAULT NULL,
`utc_timestamp_gps` BIGINT UNSIGNED NULL DEFAULT NULL,
`gps_recorded_at_utc` DATETIME NULL DEFAULT NULL,
`device_timezone` SMALLINT NOT NULL COMMENT 'UTC-Offset in Stunden aus der API',
`longitude` DECIMAL(10,7) NULL DEFAULT NULL,
`latitude` DECIMAL(9,7) NULL DEFAULT NULL,
`valid` TINYINT UNSIGNED NOT NULL,
`battery` TINYINT UNSIGNED NULL DEFAULT NULL COMMENT '0-100 Prozent; NULL = nicht verfuegbar',
`speed` SMALLINT UNSIGNED NOT NULL COMMENT 'km/h',
`fetched_at_utc` DATETIME NOT NULL COMMENT 'Zeitpunkt des Imports; UTC',
PRIMARY KEY (`device_id`),
KEY `idx_gps_recorded_at` (`gps_recorded_at_utc`),
KEY `idx_gps_license_plate` (`license_plate`),
KEY `idx_gps_fetched_at` (`fetched_at_utc`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Letzte GPS-API-Daten je Geraet; eine my-GPS-Kundenanbindung pro Tabelle';
Konfiguration des Beispiels
| Variable | Inhalt |
|---|---|
MGPS_API_KEY | Dein vollständiger API-Key |
MGPS_DB_DSN | mysql:host=127.0.0.1;port=3306;dbname=tracking;charset=utf8mb4 |
MGPS_DB_USER | Datenbankbenutzer |
MGPS_DB_PASSWORD | Datenbankpasswort |
Im Beispiel werden license_plate=-1, battery=-1 und utc_timestamp_gps=0 zu SQL-NULL. Bei valid=0 bleiben die Koordinaten leer. Ein Akkustand von 0 Prozent bleibt ein gültiger Messwert.
Häufige Fragen zur GPS Tracker API
Wie erkenne ich, ob die Position aktuell ist?
Vergleiche utc_timestamp_gps mit der aktuellen Zeit. Der Wert beschreibt den Zeitpunkt der gemeldeten GPS-Position. Wenn ein Gerät längere Zeit keine neuen Daten übertragen hat, bleibt der letzte Datenstand bestehen.
Warum steht beim Akkustand -1?
Dieser Wert bedeutet, dass kein Akkuwert verfügbar ist. Das kann beispielsweise bei einem Gerät ohne Akku oder einem Tracker ohne Übertragung dieses Werts vorkommen.
Wie sperre ich einen API-Key vorübergehend?
Pausiere ihn in der API-Key-Verwaltung im my-GPS-Portal. Du kannst ihn später wieder aktivieren. Pausierte Keys zählen weiterhin zum Limit von zwei Keys pro Benutzer.
Was passiert beim Löschen?
Der gelöschte Key ist dauerhaft ungültig. Erstelle bei Bedarf einen neuen Key und hinterlege ihn in der angebundenen Anwendung.