From 59c46a023b487d107148e4462b69cb2f3902a84d Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Fri, 14 Nov 2025 18:47:05 +0000 Subject: [PATCH] =?UTF-8?q?=C3=9Cberarbeite=20Dokumentation=20auf=20aktuel?= =?UTF-8?q?len=20MQTT-only=20Stand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md komplett neu geschrieben: - Fokus auf n8n-tracker.json (MQTT-only) - Entfernt: Telegram-Workflows, Datei-basierte Speicherung - Hinzugefügt: OwnTracks-Setup, Geräte-Mapping, erweiterte Features - Neue Sektionen: Sicherheitshinweise, DSGVO-Compliance - Praktische Code-Beispiele für Customization - CLAUDE.md aktualisiert: - Neue Workflow-Architektur dokumentiert - NocoDB-Schema mit battery/speed Feldern - Web-Interface Details (Filter, Kartenebenen, Marker) - Wichtige Gotchas und Edge Cases hinzugefügt - Dateien bereinigt: - Gelöscht: tracker.json, tracker-db.json, tracker-mqtt.json - Gelöscht: index_owntrack.html, locations-example.csv - Hinzugefügt: n8n-tracker.json (aktueller Workflow) - Hinzugefügt: database-example.csv (aktuelles Schema) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 474 +++++++++----- README.md | 1364 +++++++++++++++++++++++------------------ database-example.csv | 12 + index_owntrack.html | 224 ------- locations-example.csv | 9 - n8n-tracker.json | 290 +++++++++ tracker-db.json | 465 -------------- tracker-mqtt.json | 185 ------ tracker.json | 324 ---------- 9 files changed, 1368 insertions(+), 1979 deletions(-) create mode 100644 database-example.csv delete mode 100644 index_owntrack.html delete mode 100644 locations-example.csv create mode 100644 n8n-tracker.json delete mode 100644 tracker-db.json delete mode 100644 tracker-mqtt.json delete mode 100644 tracker.json diff --git a/CLAUDE.md b/CLAUDE.md index a3e5cd6..cac399d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,108 +4,109 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Overview -This repository contains three n8n workflow configurations for location tracking: +This repository contains an **MQTT-based location tracking system** using n8n and NocoDB: -1. **tracker.json** - Telegram-based with file storage using `/tmp/n8n-locations.json` (simpler, no database required) -2. **tracker-db.json** - Telegram-based with NocoDB storage (production-ready, persistent storage) -3. **tracker-mqtt.json** - MQTT-based with NocoDB storage (for OwnTracks/MQTT location data) +- **n8n-tracker.json** - MQTT/OwnTracks workflow with NocoDB storage +- **index.html** - Web interface with device filtering, time-based filtering, and multiple map layers -The Telegram workflows share the same architecture but differ in storage backend. The MQTT workflow integrates OwnTracks-compatible location data into the same NocoDB database. +The system subscribes to MQTT topics (OwnTracks-compatible), processes location data, stores it in NocoDB, and provides both a REST API and web visualization. ## Workflow Architecture -### Telegram-based Workflows +The **n8n-tracker.json** workflow contains two independent execution flows: -Both Telegram workflows have two main execution paths: +### 1. MQTT Location Capture Flow -**1. Location Capture Flow (Telegram Trigger → Storage)** +``` +MQTT Trigger (owntracks/#) + ↓ +MQTT Location verarbeiten (JavaScript: Parse JSON, transform data) + ↓ +Speichere in NocoDB (Create record with lat/lon, battery, speed, etc.) +``` -tracker.json (File-based): -- Telegram Trigger → Hat Location? → Location verarbeiten → Lade existierende Daten → Merge mit History → Speichere in File → Telegram Bestätigung +**Key nodes:** +- **MQTT Trigger**: Subscribes to `owntracks/#` topic, receives JSON messages from OwnTracks devices +- **MQTT Location verarbeiten**: + - Parses the `message` field (JSON string) + - Validates required fields (lat, lon, tst) + - Transforms OwnTracks format to NocoDB schema + - Extracts telemetry data (battery, velocity, accuracy, altitude, etc.) + - Converts Unix timestamp to ISO 8601 format +- **Speichere in NocoDB**: Stores location with metadata in database -tracker-db.json (NocoDB-based): -- Telegram Trigger → Hat Location? → Location verarbeiten → Speichere in NocoDB → [Hole letzten Eintrag + Zähle Einträge] → Merge → Bereite Bestätigung vor → Telegram Bestätigung +### 2. Location API Flow -Key nodes: -- **Telegram Trigger**: Receives incoming Telegram messages -- **Hat Location?**: Filters messages containing location data -- **Location verarbeiten**: Extracts and formats location data (lat/lon, user info, timestamp) using JavaScript -- **Storage**: Either file-based (shell commands) or NocoDB API calls -- **Telegram Bestätigung**: Sends confirmation with location details and map link +``` +Webhook - Location API (GET /webhook/location) + ↓ +Lade Daten aus NocoDB (Get all records) + ↓ +Format API Response (JavaScript: Sort, structure JSON) + ↓ +JSON Response (CORS-enabled) +``` -**2. Location API Flow (Webhook → JSON Response)** - See section below - -### MQTT-based Workflow - -**tracker-mqtt.json** has a simpler, single-path architecture: - -MQTT Trigger → Ist Location? → MQTT Location verarbeiten → Speichere in NocoDB - -Key nodes: -- **MQTT Trigger**: Subscribes to MQTT topic `owntracks/#` (OwnTracks-compatible) -- **Ist Location?**: Filters for messages with `_type: "location"` -- **MQTT Location verarbeiten**: Transforms MQTT/OwnTracks data format to match NocoDB schema -- **Speichere in NocoDB**: Stores location in same database as Telegram data - -### Location API Flow (Shared) - -**tracker.json (File-based)**: -- Webhook - Location API → Lade Daten für API (shell: cat) → Format API Response → JSON Response - -**tracker-db.json (NocoDB-based)**: -- Webhook - Location API → Lade Daten aus NocoDB → Format API Response → JSON Response - -Both expose `/location` endpoint with CORS enabled, returning current location, history, and metadata. The MQTT workflow shares the same NocoDB database and thus the same API. +**Key nodes:** +- **Webhook - Location API**: Public endpoint at `/webhook/location` with CORS enabled +- **Lade Daten aus NocoDB**: Fetches all location records from database +- **Format API Response**: Sorts by timestamp (newest first), builds response structure +- **JSON Response**: Returns structured JSON with CORS headers ## Key Technical Details ### Data Storage -**tracker.json (File-based)**: -- Location: `/tmp/n8n-locations.json` -- Format: JSON array of location objects -- Max retention: 100 most recent locations (oldest automatically removed) -- Persistence: Survives n8n restarts but may be lost on system restart due to `/tmp` location +**NocoDB Database Configuration:** +- **Project ID**: `pdxl4cx4dbu9nxi` +- **Table ID**: `m8pqj5ixgnnrzkg` +- **Credential ID**: `T9XuGr6CJD2W2BPO` (NocoDB Token account) +- **Persistence**: Full database persistence (no client-side limit) -**tracker-db.json & tracker-mqtt.json (NocoDB)**: -- Location: NocoDB database (Project: `pdxl4cx4dbu9nxi`, Table: `m8pqj5ixgnnrzkg`) -- Format: NocoDB records (no client-side limit) -- Persistence: Full database persistence -- API: Uses n8n's NocoDB node with token authentication (ID: `6fNBtcghMe8wFoE5`) -- Both workflows write to the same database table +### NocoDB Schema -### Location Object Structure (NocoDB Schema) +The database stores location records with the following fields: -The NocoDB database uses the following schema for all location sources: - -```json -{ - "latitude": number, - "longitude": number, - "timestamp": "ISO 8601 string", - "user_id": number, - "first_name": string, - "last_name": string, - "username": string, - "marker_label": string, - "display_time": "de-DE locale string", - "chat_id": number -} +``` +latitude (Decimal) - Geographic latitude +longitude (Decimal) - Geographic longitude +timestamp (DateTime) - ISO 8601 timestamp +user_id (Number) - Always 0 for MQTT devices +first_name (Text) - Tracker ID (e.g., "10", "11") +last_name (Text) - Source type (e.g., "fused") +username (Text) - Same as tracker ID +marker_label (Text) - Display label for map markers +display_time (Text) - Formatted timestamp (de-DE locale) +chat_id (Number) - Always 0 for MQTT devices +battery (Number) - Battery percentage (0-100) +speed (Decimal) - Velocity in m/s ``` -**Data Mapping for MQTT/OwnTracks**: -- `latitude` ← `lat` -- `longitude` ← `lon` -- `timestamp` ← `tst` (Unix timestamp converted to ISO 8601) -- `user_id` ← `0` (MQTT has no user ID) -- `first_name` ← `tid` (tracker ID, e.g., "le") -- `last_name` ← `source` (e.g., "fused") -- `username` ← `tid` -- `marker_label` ← Combination of `tid` and optionally `SSID` -- `display_time` ← Formatted timestamp -- `chat_id` ← `0` (MQTT has no chat ID) +### OwnTracks Data Mapping -Additional MQTT data (accuracy, altitude, battery, velocity, etc.) is available in the transformation node but not stored in the database. +The MQTT transformation node maps OwnTracks JSON fields to NocoDB schema: + +| NocoDB Field | OwnTracks Field | Transformation | +|--------------|-----------------|----------------| +| `latitude` | `lat` | Direct mapping | +| `longitude` | `lon` | Direct mapping | +| `timestamp` | `tst` | Unix timestamp → ISO 8601 | +| `user_id` | - | Static: `0` | +| `first_name` | `tid` | Tracker ID (device identifier) | +| `last_name` | `source` | Location source (e.g., "fused") | +| `username` | `tid` | Same as tracker ID | +| `marker_label` | `tid` | Used for map display | +| `display_time` | `tst` | Formatted with `de-DE` locale | +| `chat_id` | - | Static: `0` | +| `battery` | `batt` | Battery percentage | +| `speed` | `vel` | Velocity in m/s | + +**Additional OwnTracks data available but NOT stored:** +- `acc` - Accuracy in meters +- `alt` - Altitude +- `cog` - Course over ground +- `conn` - Connection type (w=WiFi, m=Mobile) +- `_id` - Device identifier ### API Response Structure ```json @@ -118,116 +119,257 @@ Additional MQTT data (accuracy, altitude, battery, velocity, etc.) is available } ``` -### Web Interface -The workflow sends users a link to `https://web.unixweb.home64.de/tracker/index.html` for viewing locations on a map. +### Web Interface (index.html) -The `index.html` file in this repository provides a standalone web interface: -- **Map Library**: Leaflet.js (loaded from CDN) -- **API Endpoint**: Configured to `https://n8n.unixweb.eu/webhook/location` (index.html:85) -- **Features**: Auto-refresh (5 second interval), location history polyline, marker popups -- **Hosting**: Can be hosted on any web server or opened as a local file +The web interface is a single-page application built with Leaflet.js: -## Workflow Configuration +**Configuration:** +- **API Endpoint**: `https://n8n.unixweb.home64.de/webhook/location` (line 178) +- **Default View**: Munich (48.1351, 11.5820) at zoom level 12 +- **Auto-refresh**: 5 second interval (configurable) -### Shared Configuration (Both Workflows) -- **Telegram Credentials**: "Telegram account n8n-munich-bot" (ID: `dRHgVQKqowQHIait`) -- **Webhook IDs**: - - Telegram trigger: `telegram-location-webhook` - - Location API: `location-api-endpoint` +**Key Features:** +1. **Multiple Map Layers** (lines 158-171): + - Standard (OpenStreetMap) + - Satellite (Esri World Imagery) + - Terrain (OpenTopoMap) + - Dark Mode (CartoDB Dark) + +2. **Device Mapping** (lines 142-152): + - Hardcoded device names: `'10'` → "Joachim Pixel", `'11'` → "Huawei Smartphone" + - Device-specific colors: Red (#e74c3c) for device 10, Blue (#3498db) for device 11 + - **Important**: Device names are mapped from the `username` field (which contains the tracker ID) + +3. **Filtering System**: + - **Device Filter**: Dropdown populated dynamically from available `username` values + - **Time Filter**: 1h, 3h, 6h, 12h, 24h (default: 1 hour) + - Filter logic: Always filters to `user_id == 0` (MQTT-only), then applies device and time filters + +4. **Visualization** (lines 284-376): + - **Markers**: Circular SVG icons with navigation-style clock hand + - **Size**: Latest location = 32x32px, history = 16x16px + - **Colors**: Device-specific colors from `DEVICE_COLORS` mapping + - **Polylines**: Shows movement path per device, color-coded + - **Popups**: Show device name, timestamp, battery %, speed (km/h) + +**Important Implementation Details:** +- The `username` field filter logic (line 267) filters MQTT data by checking `user_id == 0` +- Device colors and names must be updated in the hardcoded mappings (lines 142-152) +- Speed conversion: OwnTracks velocity (m/s) is converted to km/h with `speed * 3.6` (line 329) + +## Workflow Configuration (n8n-tracker.json) + +**Workflow Settings:** +- **Name**: "Telegram Location Tracker - NocoDB" +- **Workflow ID**: `6P6dKqi4IKcJ521m` +- **Version ID**: `de17706a-a0ea-42ce-a069-dd09dce421d2` - **Execution Order**: v1 - **Caller Policy**: workflowsFromSameOwner -- **Status**: Both workflows set to `active: true` by default +- **Status**: `active: true` +- **Error Workflow**: `0bBZzSE6SUzVsif5` +- **Tags**: "owntrack" -### tracker.json Specific -- **Storage**: Shell commands (cat, echo) for file I/O -- **Error Workflow**: ID `PhwIkaqyXRasTXDH` (configured but not in this export) +**Credentials:** +- **MQTT**: Credential ID `L07VVR2BDfDda6Zo` ("MQTT account") +- **NocoDB**: Credential ID `T9XuGr6CJD2W2BPO` ("NocoDB Token account") -### tracker-db.json Specific -- **NocoDB Credentials**: "NocoDB Token account" (ID: `6fNBtcghMe8wFoE5`) -- **Project ID**: `pdxl4cx4dbu9nxi` -- **Table ID**: `m8pqj5ixgnnrzkg` -- **Sort Order**: Uses array structure `[{field: "timestamp", direction: "desc"}]` for sorting locations +**Node Configuration:** +- **MQTT Trigger**: + - Topic: `owntracks/#` + - Subscribes to all OwnTracks topics + - No message filtering at trigger level (all messages pass through) -### tracker-mqtt.json Specific -- **MQTT Credentials**: Requires MQTT broker credentials (placeholder ID: `MQTT_CREDENTIAL_ID`) -- **Topic**: `owntracks/#` (subscribes to all OwnTracks topics) -- **Message Filter**: Only processes messages with `_type: "location"` -- **NocoDB Config**: Same as tracker-db.json (shares database) -- **Status**: Set to `active: false` by default (activate after configuring MQTT credentials) +- **MQTT Location verarbeiten** (Code Node): + - Parses JSON from `message` field + - Validates required fields: `lat`, `lon`, `tst` + - Skips invalid messages with `continue` + - Sets `alwaysOutputData: true` to handle empty results + - Timezone: Europe/Berlin for `display_time` -## Modifying the Workflows +- **Speichere in NocoDB**: + - Operation: `create` + - Maps 12 fields from JSON to NocoDB columns + - Includes telemetry: `battery` (from `mqtt_data.battery`), `speed` (from `mqtt_data.velocity`) -### Important Considerations -1. **Both workflows are active by default** - test changes carefully to avoid disrupting live tracking -2. **JavaScript code nodes** use n8n's execution environment (not vanilla Node.js) -3. **Date formatting** uses `de-DE` locale - change in "Location verarbeiten" node if needed -4. **CORS** is configured for `*` (all origins) - restrict for production security +- **Webhook - Location API**: + - Path: `/location` + - Webhook ID: `location-api-endpoint` + - Response Mode: `lastNode` + - CORS: Allowed origins = `*` -### tracker.json Specific -- Shell commands execute in n8n's runtime environment - ensure `/tmp` is writable -- The 100-entry limit prevents unbounded growth - adjust in "Merge mit History" node -- Consider moving from `/tmp` to persistent storage for production (see README.md) +## Common Modifications -### tracker-db.json & tracker-mqtt.json Specific -- NocoDB sorting requires array structure: `[{field: "timestamp", direction: "desc"}]` -- No client-side entry limit (relies on database capacity) -- Requires valid NocoDB credentials and accessible database -- Both workflows write to the same NocoDB table +### Adding a New Device -### tracker-mqtt.json Specific -- Requires MQTT broker configuration before activation -- Topic pattern can be adjusted to match your MQTT setup -- Data transformation maps OwnTracks format to NocoDB schema -- MQTT data fields (accuracy, battery, velocity) are extracted but not persisted to database +**Step 1: Update index.html device mappings (lines 142-152)** +```javascript +const DEVICE_NAMES = { + '10': 'Joachim Pixel', + '11': 'Huawei Smartphone', + '12': 'New Device Name' // Add this line +}; -### Common Modifications +const DEVICE_COLORS = { + '10': '#e74c3c', + '11': '#3498db', + '12': '#2ecc71', // Add this line (green) + 'default': '#95a5a6' +}; +``` -**Change history limit (tracker.json only)**: -In "Merge mit History" node, change `locations.slice(0, 100)` to desired limit +**Step 2: Configure OwnTracks app** +- Set Tracker ID (`tid`) to match the key (e.g., "12") +- Topic will be `owntracks/user/12` +- The workflow automatically picks up new devices -**Change date format**: -In "Location verarbeiten" node, change `.toLocaleString('de-DE')` to desired locale +### Changing Date/Time Format -**Restrict CORS**: -In "Webhook - Location API" node, change `Access-Control-Allow-Origin: *` to specific domain +**In n8n workflow node "MQTT Location verarbeiten" (line 124):** +```javascript +// Current: German format with Berlin timezone +const displayTime = new Date(timestampMs).toLocaleString('de-DE', { timeZone: 'Europe/Berlin' }); -**Update web interface URL**: -In "Telegram Bestätigung" node and `index.html:85`, update API endpoint URL +// Change to US format: +const displayTime = new Date(timestampMs).toLocaleString('en-US', { timeZone: 'America/New_York' }); + +// Change to ISO format: +const displayTime = new Date(timestampMs).toISOString(); +``` + +### Restricting CORS (Security) + +**In n8n workflow node "Webhook - Location API" (lines 65-75):** +```javascript +// Current (insecure): +{ "name": "Access-Control-Allow-Origin", "value": "*" } + +// Change to specific domain: +{ "name": "Access-Control-Allow-Origin", "value": "https://web.unixweb.home64.de" } +``` + +### Adding New NocoDB Fields + +**Example: Store accuracy and altitude** + +**Step 1: Add columns in NocoDB:** +- `accuracy` (Number) +- `altitude` (Number) + +**Step 2: Update "MQTT Location verarbeiten" node (line 124):** +```javascript +mqtt_data: { + accuracy: mqttData.acc, + altitude: mqttData.alt, + battery: mqttData.batt, + velocity: mqttData.vel, + // ... existing fields +} +``` + +**Step 3: Update "Speichere in NocoDB" node to map new fields:** +```javascript +{ "fieldName": "accuracy", "fieldValue": "={{ $json.mqtt_data.accuracy }}" }, +{ "fieldName": "altitude", "fieldValue": "={{ $json.mqtt_data.altitude }}" } +``` + +### Changing MQTT Topic Filter + +**In node "MQTT Trigger" (line 104):** +```javascript +// Current: All OwnTracks topics +topics: "owntracks/#" + +// Change to specific user: +topics: "owntracks/joachim/#" + +// Change to specific device: +topics: "owntracks/joachim/pixel" + +// Multiple topics: +topics: "owntracks/joachim/#,owntracks/lisa/#" +``` + +### Updating API Endpoint URL + +**In index.html (line 178):** +```javascript +// Current: +const API_URL = 'https://n8n.unixweb.home64.de/webhook/location'; + +// Change to your n8n instance: +const API_URL = 'https://your-n8n.example.com/webhook/location'; +``` + +### Changing Auto-Refresh Interval + +**In index.html (line 419):** +```javascript +// Current: 5 seconds +refreshInterval = setInterval(loadLocations, 5000); + +// Change to 10 seconds: +refreshInterval = setInterval(loadLocations, 10000); + +// Change to 30 seconds: +refreshInterval = setInterval(loadLocations, 30000); +``` ## Repository Contents -- **tracker.json** - Telegram with file-based storage (simple, no database) -- **tracker-db.json** - Telegram with NocoDB storage (production-ready) -- **tracker-mqtt.json** - MQTT/OwnTracks with NocoDB storage (IoT devices) -- **index.html** - Leaflet.js web interface for map visualization -- **locations-example.csv** - Example data format for testing -- **README.md** - Detailed German documentation with setup and usage instructions +| File | Description | +|------|-------------| +| `n8n-tracker.json` | n8n workflow - MQTT location capture + API endpoint | +| `index.html` | Web interface with multi-layer maps and device filtering | +| `database-example.csv` | Sample NocoDB export showing actual data structure | +| `README.md` | Comprehensive German documentation (setup, usage, troubleshooting) | +| `CLAUDE.md` | This file - technical architecture documentation | -## MQTT/OwnTracks Integration +## Important Gotchas and Edge Cases -The **tracker-mqtt.json** workflow is designed for OwnTracks-compatible MQTT location data. +### 1. Device Identification via `username` Field +- The `username` field contains the OwnTracks tracker ID (`tid`), not a username +- The web interface filters devices by `username`, not by `first_name` or `marker_label` +- **Example**: Device with `tid: "10"` will have `username: "10"` in database +- Device names are hardcoded in `index.html` (lines 142-145) - must be manually updated -### Setup Requirements -1. Configure MQTT broker credentials in n8n -2. Update the credential ID in tracker-mqtt.json (currently placeholder: `MQTT_CREDENTIAL_ID`) -3. Import workflow into n8n -4. Activate the workflow +### 2. MQTT Message Validation +- The workflow does NOT filter by `_type: "location"` despite what older documentation says +- All MQTT messages are processed; validation happens in the JavaScript code node +- Messages missing `lat`, `lon`, or `tst` are silently skipped with `continue` +- **Result**: Non-location MQTT messages don't cause errors, they're just ignored -### OwnTracks Configuration -Configure your OwnTracks app/device to publish to the same MQTT broker: -- **Topic pattern**: `owntracks/user/device` (workflow subscribes to `owntracks/#`) -- **Mode**: MQTT -- **Expected message format**: JSON with `_type: "location"` +### 3. Time Filter Default +- The web interface defaults to **1 hour** time filter (line 125) +- This means newly deployed users won't see historical data unless they change the filter +- Consider changing default to `24h` or `all` for better initial experience -### Data Flow -1. OwnTracks device publishes location to MQTT broker -2. n8n MQTT trigger receives message -3. Filter checks for `_type: "location"` -4. Data transformation maps MQTT fields to NocoDB schema -5. Location stored in same database as Telegram locations -6. Available via same `/location` API endpoint +### 4. Circular Marker Icon Implementation +- Markers use SVG `divIcon` with a navigation-style clock hand (lines 337-345) +- The clock hand is purely decorative, does NOT represent actual direction/heading +- This replaced standard Leaflet pin icons in recent commits (see commit 4bec87d) -### Distinguishing Data Sources -In the database: -- **Telegram entries**: Have real `user_id` and `chat_id` values, `first_name`/`last_name` from Telegram profile -- **MQTT entries**: Have `user_id: 0` and `chat_id: 0`, use `tid` (tracker ID) in name fields +### 5. Speed Unit Conversion +- OwnTracks sends velocity in **m/s** (`vel` field) +- Stored in database as m/s in `speed` column +- Converted to km/h in web UI with `speed * 3.6` (line 329) +- **Important**: If you display speed elsewhere, remember to convert + +### 6. Battery Data May Be Null +- Not all OwnTracks messages include battery data +- The code checks for `battery !== undefined && battery !== null` before displaying (line 323) +- Same applies to `speed` field (line 328) + +### 7. CORS Configuration +- API has CORS set to `*` (all origins allowed) +- This is intentional for development but **insecure for production** +- See "Restricting CORS" section for how to fix + +### 8. Error Workflow Reference +- The workflow references error workflow ID `0bBZzSE6SUzVsif5` +- This error workflow is NOT included in the repository export +- If importing to a new n8n instance, errors will fail silently without this workflow + +### 9. Timezone Handling +- All timestamps are converted to Europe/Berlin timezone (line 124 in workflow) +- This is hardcoded in the JavaScript transformation +- Database stores ISO 8601 UTC timestamps, but `display_time` is Berlin time diff --git a/README.md b/README.md index 1b4694f..a23ec2d 100644 --- a/README.md +++ b/README.md @@ -1,200 +1,307 @@ # Location Tracker für n8n -Ein umfassendes n8n-Workflow-System zur Standort-Verfolgung mit mehreren Datenquellen und erweiterten Visualisierungsmöglichkeiten. +Ein MQTT-basiertes Location-Tracking-System mit n8n, NocoDB und interaktiver Web-Visualisierung für OwnTracks-Geräte. ## Überblick -Dieses Repository enthält **drei n8n-Workflows** für Location Tracking mit verschiedenen Speicher- und Datenquellen-Optionen: +Dieses Repository enthält ein **MQTT-basiertes Location-Tracking-System** mit folgenden Komponenten: -1. **tracker.json** - Telegram-basiert mit Datei-Speicherung (einfach, keine Datenbank) -2. **tracker-db.json** - Telegram-basiert mit NocoDB-Speicherung (produktionsreif, persistent) -3. **tracker-mqtt.json** - MQTT-basiert mit NocoDB-Speicherung (für OwnTracks/IoT-Geräte) +- **n8n-tracker.json** - n8n-Workflow zur MQTT-Datenerfassung und API-Bereitstellung +- **index.html** - Interaktive Web-Oberfläche mit Leaflet.js -Zusätzlich bietet das Repository **zwei Web-Oberflächen** zur Visualisierung: -- **index.html** - Erweiterte Oberfläche mit Filterung, mehreren Kartenebenen und Multi-Source-Support -- **index_owntrack.html** - Vereinfachte Oberfläche mit MQTT-spezifischen Features (Batterie, Geschwindigkeit) +Das System empfängt Location-Updates von OwnTracks-kompatiblen Geräten über MQTT, speichert diese in einer NocoDB-Datenbank und bietet sowohl eine REST-API als auch eine Web-Visualisierung mit Echtzeit-Updates. ## Funktionen ### Workflow-Features -- **Multi-Source-Erfassung**: Standorte über Telegram-Bot oder MQTT/OwnTracks -- **Flexible Speicherung**: Wahl zwischen Datei-basiert (einfach) oder NocoDB (persistent, skalierbar) -- **Historien-Verwaltung**: - - tracker.json: Letzte 100 Standorte (konfigurierbar) - - tracker-db.json / tracker-mqtt.json: Unbegrenzt (Datenbank-basiert) -- **REST-API**: Einheitlicher `/location` Endpunkt für alle Workflows -- **Telegram-Benachrichtigungen**: Automatische Bestätigung mit Koordinaten und Kartenlink -- **Echtzeit-Updates**: 5-Sekunden Auto-Refresh für Live-Tracking +- **MQTT-Erfassung**: Automatischer Empfang von OwnTracks-Standortdaten über MQTT +- **Persistente Speicherung**: Unbegrenzte Historie in NocoDB-Datenbank +- **Telemetrie-Daten**: Batteriestatus und Geschwindigkeit werden mitgespeichert +- **REST-API**: JSON-Endpunkt für externe Anwendungen +- **Fehlerbehandlung**: Validierung und Fehlertoleranz bei ungültigen MQTT-Nachrichten ### Web-Oberflächen-Features -- **📍 Interaktive Karten** mit Leaflet.js -- **🗺️ Mehrere Kartenebenen**: Standard, Satellit, Gelände, Dunkel-Modus -- **📡 Datenquellen-Filter**: Telegram, MQTT oder kombiniert -- **👤 Benutzer/Geräte-Filter**: Separate Ansicht pro Person/Gerät -- **⏱️ Zeitfilter**: 1h, 6h, 24h, 7 Tage, 30 Tage -- **🔄 Toggle Auto-Refresh**: An/Aus-Schaltung für Live-Updates -- **📊 Standort-Historie**: Polyline-Darstellung des Bewegungspfads -- **🔋 MQTT-Telemetrie**: Batterie, Geschwindigkeit, Genauigkeit (index_owntrack.html) +- **📍 Interaktive Karte** mit Leaflet.js +- **🗺️ 4 Kartenebenen**: Standard (OpenStreetMap), Satellit (Esri), Gelände (OpenTopoMap), Dunkel-Modus (CartoDB) +- **📱 Geräte-Filter**: Separate Ansicht pro Gerät +- **⏱️ Zeitfilter**: 1h, 3h, 6h, 12h, 24h +- **🔄 Auto-Refresh**: Toggle-fähig, 5-Sekunden-Intervall +- **📊 Bewegungshistorie**: Farbcodierte Polyline-Darstellung pro Gerät +- **🔋 Telemetrie-Anzeige**: Batteriestatus und Geschwindigkeit in Popups +- **🎨 Geräte-spezifische Farben**: Unterschiedliche Farben pro Gerät ## Voraussetzungen -### Basis-Anforderungen (alle Workflows) -- Eine laufende n8n-Instanz (Version 1.0+) -- Zugriff auf n8n-Credentials-Management +### Basis-Anforderungen +- Eine laufende **n8n-Instanz** (Version 1.0+) +- **NocoDB-Instanz** mit API-Zugriff +- **MQTT-Broker** (z.B. Mosquitto) +- **OwnTracks-App** oder kompatibles MQTT-Gerät -### Workflow-spezifische Anforderungen +### MQTT-Broker +Wenn noch kein MQTT-Broker vorhanden ist: -**tracker.json (Datei-basiert)**: -- Schreibrechte für `/tmp/n8n-locations.json` auf dem n8n-Server -- Telegram-Bot mit gültigem API-Token +```bash +# Ubuntu/Debian +sudo apt install mosquitto mosquitto-clients -**tracker-db.json (NocoDB)**: -- NocoDB-Instanz mit API-Zugriff -- NocoDB-Token mit Schreibrechten -- Telegram-Bot mit gültigem API-Token +# Mosquitto starten +sudo systemctl start mosquitto +sudo systemctl enable mosquitto -**tracker-mqtt.json (MQTT)**: -- MQTT-Broker (z.B. Mosquitto) -- MQTT-Credentials mit Subscribe-Rechten auf `owntracks/#` -- NocoDB-Instanz (siehe tracker-db.json) -- OwnTracks-App oder kompatibles MQTT-Gerät +# Test +mosquitto_sub -h localhost -p 1883 -t 'owntracks/#' -v +``` ## Installation -### Schritt 1: Workflow wählen und importieren +### Schritt 1: n8n-Workflow importieren -Wähle den passenden Workflow für deinen Anwendungsfall: - -| Workflow | Empfohlen für | Vorteile | Nachteile | -|----------|---------------|----------|-----------| -| **tracker.json** | Testen, Prototyping | Einfach, keine DB nötig | Begrenzte Historie, /tmp-Speicher | -| **tracker-db.json** | Produktion (Telegram) | Persistent, unbegrenzt | NocoDB erforderlich | -| **tracker-mqtt.json** | IoT-Geräte, OwnTracks | Multi-Gerät-Support | MQTT-Broker + NocoDB | - -**Import-Schritte**: 1. Öffne deine n8n-Instanz -2. Navigiere zu "Workflows" → "Import from File" -3. Wähle die gewünschte `.json` Datei aus +2. Navigiere zu **Workflows** → **Import from File** +3. Wähle `n8n-tracker.json` aus diesem Repository +4. Workflow wird als "Telegram Location Tracker - NocoDB" importiert (Name kann angepasst werden) -### Schritt 2: Credentials konfigurieren +### Schritt 2: NocoDB-Datenbank einrichten -#### Telegram-Bot (tracker.json & tracker-db.json) +#### NocoDB-Tabelle erstellen -1. Erstelle einen Bot über [@BotFather](https://t.me/botfather): +1. Erstelle ein neues Project in NocoDB +2. Erstelle eine Tabelle mit folgendem Schema: + +| Spaltenname | Datentyp | Beschreibung | +|-------------|----------|--------------| +| `latitude` | Decimal | Breitengrad | +| `longitude` | Decimal | Längengrad | +| `timestamp` | DateTime | Zeitstempel (ISO 8601) | +| `user_id` | Number | Immer 0 für MQTT | +| `first_name` | Text | Tracker-ID (z.B. "10") | +| `last_name` | Text | Source-Typ (z.B. "fused") | +| `username` | Text | Tracker-ID (wie first_name) | +| `marker_label` | Text | Anzeigename für Karte | +| `display_time` | Text | Formatierter Zeitstempel | +| `chat_id` | Number | Immer 0 für MQTT | +| `battery` | Number | Batteriestatus (0-100) | +| `speed` | Decimal | Geschwindigkeit in m/s | + +3. Notiere **Project ID** und **Table ID** aus der NocoDB-URL: ``` - /newbot - Wähle Name: "My Location Tracker" - Wähle Username: "my_location_tracker_bot" + https://nocodb.example.com/nc/PROJECT_ID/TABLE_ID ``` -2. Kopiere das API-Token (Format: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`) -3. In n8n: - - Gehe zu "Credentials" → "Create New" - - Wähle "Telegram API" - - Gib das Access Token ein - - Speichere als "Telegram account n8n-munich-bot" (oder passe Workflow-Nodes an) -#### NocoDB (tracker-db.json & tracker-mqtt.json) +#### NocoDB API-Token generieren -1. Erstelle in NocoDB: - - Ein neues Project - - Eine Tabelle mit dem Schema (siehe unten) -2. Generiere einen API-Token: - - NocoDB: Account Settings → Tokens → Create Token -3. In n8n: - - Gehe zu "Credentials" → "Create New" - - Wähle "NocoDB API Token" - - Gib Token und Base-URL ein - - Notiere die Credential-ID für den Workflow +1. In NocoDB: **Account Settings** → **Tokens** → **Create Token** +2. Kopiere den generierten Token -**NocoDB Tabellen-Schema**: -``` -Tabelle: Locations -- latitude (Decimal) -- longitude (Decimal) -- timestamp (DateTime) -- user_id (Number) -- first_name (Text) -- last_name (Text) -- username (Text) -- marker_label (Text) -- display_time (Text) -- chat_id (Number) +### Schritt 3: Credentials in n8n konfigurieren + +#### MQTT-Credentials + +1. In n8n: **Credentials** → **Create New** +2. Wähle **"MQTT"** +3. Konfiguriere: + - **Protocol**: mqtt (oder mqtts für TLS) + - **Host**: Dein MQTT-Broker (z.B. `localhost` oder `broker.example.com`) + - **Port**: 1883 (Standard) oder 8883 (TLS) + - **Username**: MQTT-Benutzername + - **Password**: MQTT-Passwort +4. Speichere als "MQTT account" + +#### NocoDB-Credentials + +1. In n8n: **Credentials** → **Create New** +2. Wähle **"NocoDB API Token"** +3. Konfiguriere: + - **API Token**: Token aus Schritt 2 + - **Base URL**: NocoDB-URL (z.B. `https://nocodb.example.com`) +4. Speichere als "NocoDB Token account" + +### Schritt 4: Workflow-IDs anpassen + +Öffne den importierten Workflow in n8n und passe an: + +**In den Nodes "Lade Daten aus NocoDB" und "Speichere in NocoDB":** +- **Project ID**: Deine NocoDB-Projekt-ID (ersetze `pdxl4cx4dbu9nxi`) +- **Table ID**: Deine NocoDB-Tabellen-ID (ersetze `m8pqj5ixgnnrzkg`) + +**Credential-Zuordnung prüfen:** +- MQTT Trigger → Wähle deine "MQTT account" Credentials +- NocoDB-Nodes → Wähle deine "NocoDB Token account" Credentials + +### Schritt 5: OwnTracks-App konfigurieren + +1. **OwnTracks-App installieren** (Android/iOS) + +2. **MQTT-Modus aktivieren:** + - Öffne OwnTracks → **Preferences** + - **Mode**: MQTT + - **Host**: Dein MQTT-Broker (z.B. `broker.example.com`) + - **Port**: 1883 (oder 8883 für TLS) + - **Username**: MQTT-Benutzername + - **Password**: MQTT-Passwort + - **Device ID** (tid): z.B. "10" oder "11" (wichtig für Geräte-Identifikation!) + - **Tracker ID** (tid): Gleicher Wert wie Device ID + +3. **TLS/Verschlüsselung** (optional aber empfohlen): + - Port auf 8883 ändern + - TLS aktivieren + +4. **Tracking-Einstellungen:** + - **Monitoring**: Signifikante Standortänderungen + - **Move Mode**: 100m (oder nach Bedarf) + +### Schritt 6: Web-Oberfläche konfigurieren + +#### API-Endpunkt anpassen + +Öffne `index.html` und passe die API-URL an (Zeile 178): + +```javascript +const API_URL = 'https://deine-n8n-instanz.de/webhook/location'; ``` -#### MQTT-Broker (tracker-mqtt.json) +**Webhook-URL finden:** +- In n8n: Öffne den Workflow +- Klicke auf den Node "Webhook - Location API" +- Die URL steht unter "Webhook URLs" (z.B. `https://n8n.example.com/webhook/location`) -1. Installiere einen MQTT-Broker (z.B. Mosquitto): +#### Geräte-Namen konfigurieren + +Passe die Geräte-Zuordnung in `index.html` an (Zeilen 142-152): + +```javascript +const DEVICE_NAMES = { + '10': 'Joachim Pixel', // Device ID '10' → Anzeigename + '11': 'Huawei Smartphone' // Device ID '11' → Anzeigename +}; + +const DEVICE_COLORS = { + '10': '#e74c3c', // Rot + '11': '#3498db', // Blau + 'default': '#95a5a6' // Grau für unbekannte Geräte +}; +``` + +**Wichtig:** Die Keys (`'10'`, `'11'`) müssen mit der **Tracker ID (tid)** aus OwnTracks übereinstimmen! + +#### Web-Oberfläche hosten + +**Option 1: Webserver (empfohlen)** +```bash +# Apache +sudo cp index.html /var/www/html/tracker/ + +# nginx +sudo cp index.html /usr/share/nginx/html/tracker/ +``` + +**Option 2: Lokaler Test** +- Öffne `index.html` direkt im Browser +- Funktioniert nur, wenn CORS korrekt konfiguriert ist + +**Option 3: Static Hosting** +- GitHub Pages +- Netlify +- Vercel + +### Schritt 7: Workflow aktivieren und testen + +1. **Workflow aktivieren:** + - In n8n: Öffne den Workflow + - Klicke auf **"Active"** (Toggle oben rechts) + - Prüfe, dass alle Nodes grün sind (keine roten Fehler) + +2. **Testen:** + - Öffne OwnTracks-App + - Sende einen Location-Update (App sendet automatisch oder manuell triggern) + - Prüfe in n8n die **Execution History** + - Öffne die Web-Oberfläche → Standort sollte erscheinen + +3. **API-Test:** ```bash - # Ubuntu/Debian - sudo apt install mosquitto mosquitto-clients + curl https://deine-n8n-instanz.de/webhook/location ``` -2. In n8n: - - Gehe zu "Credentials" → "Create New" - - Wähle "MQTT" - - Gib Broker-URL, Port, Username, Passwort ein -3. Passe im Workflow die Credential-ID an (aktuell Platzhalter: `MQTT_CREDENTIAL_ID`) - -### Schritt 3: Workflow-IDs anpassen (nur bei NocoDB) - -Öffne den importierten Workflow und passe an: -- **Project ID**: Deine NocoDB-Projekt-ID -- **Table ID**: Deine NocoDB-Tabellen-ID - -Diese findest du in der NocoDB-URL: -``` -https://nocodb.example.com/nc/PROJECT_ID/TABLE_ID -``` - -### Schritt 4: Workflow aktivieren - -1. Öffne den importierten Workflow -2. Prüfe alle Credentials (rote Nodes = fehlende/falsche Credentials) -3. Klicke auf "Active" um den Workflow zu aktivieren - -### Schritt 5: Testen - -**Telegram-Workflows**: -1. Öffne deinen Telegram-Bot -2. Sende einen Standort (📎 → Standort) -3. Du solltest eine Bestätigungsnachricht erhalten - -**MQTT-Workflow**: -1. Konfiguriere OwnTracks-App mit deinem MQTT-Broker -2. Sende einen Location-Update -3. Prüfe in n8n die Workflow-Execution-Historie + Sollte JSON zurückgeben mit `success: true` und Location-Daten ## Verwendung -### Standort senden (Telegram) +### Standort senden (OwnTracks) -1. Öffne den Chat mit deinem Telegram-Bot -2. Klicke auf das Büroklammer-Symbol (📎) -3. Wähle "Standort" -4. Sende deinen aktuellen Standort oder wähle einen auf der Karte -5. Der Bot bestätigt mit Details und einem Link zur Web-Ansicht +Die OwnTracks-App sendet automatisch Location-Updates basierend auf deinen Einstellungen: -### Standort senden (MQTT/OwnTracks) +- **Automatisch**: Bei signifikanten Standortänderungen +- **Manuell**: In der App auf "Publish" klicken +- **Intervall**: Konfigurierbar in App-Einstellungen -1. **OwnTracks-App konfigurieren**: - - Mode: MQTT - - Host: Dein MQTT-Broker - - Port: 1883 (oder dein Port) - - Username/Password: Deine MQTT-Credentials - - Device ID: z.B. "le" (wird als Marker-Label verwendet) +**MQTT-Topic-Format:** +``` +owntracks/user/device +``` +Beispiel: `owntracks/joachim/pixel` -2. **Tracking starten**: - - OwnTracks sendet automatisch Location-Updates - - Konfiguriere Intervall und Genauigkeit in der App +**Nachrichtenformat (JSON):** +```json +{ + "_type": "location", + "lat": 48.1351, + "lon": 11.5820, + "tst": 1700000000, + "tid": "10", + "batt": 85, + "vel": 5, + "acc": 10, + "alt": 520 +} +``` -### REST-API abrufen +### Web-Oberfläche verwenden -Alle Workflows stellen den gleichen API-Endpunkt zur Verfügung: +#### Filter-Optionen -```bash +**🗺️ Kartenebene:** +- **Standard**: OpenStreetMap (gut für Navigation) +- **Satellit**: Esri World Imagery (Luftbild) +- **Gelände**: OpenTopoMap (Höhenlinien) +- **Dunkel**: CartoDB Dark (Nachtmodus) + +**📱 Gerät-Filter:** +- **Alle Geräte**: Zeigt alle MQTT-Geräte +- **Einzelnes Gerät**: Wähle aus Dropdown (wird dynamisch befüllt) + +**⏱️ Zeitfilter:** +- **1 Stunde**: Nur letzte Stunde (Standard) +- **3/6/12/24 Stunden**: Weitere Zeiträume +- Alle älteren Punkte werden ausgeblendet + +**🔄 Auto-Refresh:** +- **AN** (grün): Aktualisiert alle 5 Sekunden +- **AUS** (rot): Keine automatische Aktualisierung + +#### Karte verstehen + +**Marker:** +- **Größe**: Größter Marker = neuester Standort (32x32px), kleinere = Historie (16x16px) +- **Farbe**: Geräte-spezifisch (siehe `DEVICE_COLORS` Konfiguration) +- **Icon**: Kreisförmig mit dekorativem Zeiger (kein tatsächlicher Richtungsindikator) + +**Polylines:** +- Verbinden Standorte chronologisch +- Farbe entspricht Gerät +- Zeigen Bewegungspfad + +**Popups:** +- Klicke auf Marker für Details +- Zeigt: Gerätename, Zeitstempel, Batterie %, Geschwindigkeit (km/h) + +### REST-API verwenden + +**Endpunkt:** +``` GET https://deine-n8n-instanz.de/webhook/location ``` -**Beispiel-Antwort**: +**Beispiel-Antwort:** ```json { "success": true, @@ -202,587 +309,631 @@ GET https://deine-n8n-instanz.de/webhook/location "latitude": 48.1351, "longitude": 11.5820, "timestamp": "2025-11-14T10:30:00.000Z", - "user_id": 123456789, - "first_name": "Max", - "last_name": "Mustermann", - "username": "maxmuster", - "marker_label": "Max Mustermann", + "user_id": 0, + "first_name": "10", + "last_name": "fused", + "username": "10", + "marker_label": "10", "display_time": "14.11.2025, 11:30:00", - "chat_id": 123456789 + "chat_id": 0, + "battery": 85, + "speed": 5.2 }, - "history": [...], + "history": [ + { /* weitere Location-Objekte */ } + ], "total_points": 42, "last_updated": "2025-11-14T10:30:00.000Z" } ``` -**MQTT-spezifische Felder** (nur in index_owntrack.html angezeigt): -```json -{ - "battery": 85, - "speed": 5.2, - "accuracy": 10, - "altitude": 520 -} -``` - -### Web-Oberflächen - -Das Repository enthält zwei Web-Interfaces mit unterschiedlichen Features: - -#### index.html - Erweiterte Multi-Source-Oberfläche - -**Empfohlen für**: Produktionsumgebungen mit mehreren Datenquellen - -**Features**: -- 🗺️ **4 Kartenebenen**: Standard (OSM), Satellit (Esri), Gelände (OpenTopoMap), Dunkel (CartoDB) -- 📡 **Datenquellen-Filter**: Telegram, MQTT oder alle -- 👤 **Benutzer/Gerät-Filter**: Dynamische Liste aller aktiven Quellen -- ⏱️ **Zeitfilter**: 1h, 6h, 24h, 7d, 30d oder alle -- 📊 **Erweiterte Visualisierung**: Farbcodierte Marker (rot=neuester, blau=Historie) -- 🔄 **Auto-Refresh**: Toggle-fähig, 5-Sekunden-Intervall - -**Verwendung**: -1. Öffne `index.html` im Browser -2. Nutze die Filter-Dropdowns zur Datenauswahl: - - **Kartenebene**: Wähle zwischen Standard, Satellit, Gelände, Dunkel - - **Datenquelle**: Telegram, MQTT oder beide - - **Benutzer/Gerät**: Filter nach spezifischem User/Device - - **Zeitraum**: Begrenze Historie auf gewünschten Zeitraum -3. Klicke Marker für Details -4. Toggle Auto-Refresh nach Bedarf - -#### index_owntrack.html - MQTT/OwnTracks-fokussierte Oberfläche - -**Empfohlen für**: OwnTracks-Nutzer, die Telemetrie-Daten benötigen - -**Features**: -- 🔋 **Batteriestatus**: Anzeige des Gerätebatteriestands -- 🚗 **Geschwindigkeitsanzeige**: km/h-Anzeige aus MQTT-Daten -- 📍 **Vereinfachte Ansicht**: Fokus auf aktuellen Standort -- 🔄 **Auto-Refresh**: Gleicher Toggle wie index.html - -**Verwendung**: -1. Öffne `index_owntrack.html` im Browser -2. Die Karte zeigt automatisch den neuesten OwnTracks-Standort -3. Popups enthalten MQTT-spezifische Daten (Batterie, Speed) - -### Konfiguration der Web-Oberflächen - -**API-Endpunkt anpassen**: - -In beiden HTML-Dateien die API-URL ändern: +**Integration in eigene Apps:** ```javascript -// Für index.html (Zeile 175) -// Für index_owntrack.html (Zeile 85) -const API_URL = 'https://deine-n8n-instanz.de/webhook/location'; +// JavaScript Beispiel +fetch('https://n8n.example.com/webhook/location') + .then(response => response.json()) + .then(data => { + console.log('Aktueller Standort:', data.current); + console.log('Batterie:', data.current.battery + '%'); + }); ``` -**Deployment-Optionen**: -1. **Webserver-Hosting** (empfohlen für Produktion): - ```bash - # Apache - cp index.html /var/www/html/tracker/ +```python +# Python Beispiel +import requests - # nginx - cp index.html /usr/share/nginx/html/tracker/ - ``` +response = requests.get('https://n8n.example.com/webhook/location') +data = response.json() -2. **Lokaler Test**: - - Öffne die `.html` Datei direkt im Browser - - Funktioniert nur, wenn CORS korrekt konfiguriert ist - -3. **GitHub Pages / Static Hosting**: - - Pushe die HTML-Dateien zu GitHub - - Aktiviere GitHub Pages - - Oder nutze Netlify, Vercel, etc. - -**CORS-Konfiguration**: -Die n8n-Workflows haben CORS bereits aktiviert (`Access-Control-Allow-Origin: *`). Für Produktion sollte dies eingeschränkt werden (siehe Sicherheitshinweise) +if data['success']: + current = data['current'] + print(f"Position: {current['latitude']}, {current['longitude']}") + print(f"Batterie: {current['battery']}%") +``` ## Workflow-Architektur -### tracker.json (Datei-basiert) +### Übersicht + +Der **n8n-tracker.json** Workflow besteht aus zwei unabhängigen Flows: -**Standort-Erfassung**: ``` -Telegram Trigger - ↓ -Hat Location? (Filter) - ↓ -Location verarbeiten (JS: Daten extrahieren & formatieren) - ↓ -Lade existierende Daten (Shell: cat /tmp/n8n-locations.json) - ↓ -Merge mit History (JS: Array merge + 100-Entry-Limit) - ↓ -Speichere in File (Shell: echo > /tmp/n8n-locations.json) - ↓ -Telegram Bestätigung (Nachricht mit Koordinaten & Kartenlink) +Flow 1: MQTT Location Capture +┌──────────────┐ +│ MQTT Trigger │ (owntracks/#) +└──────┬───────┘ + │ + v +┌──────────────────────────┐ +│ MQTT Location verarbeiten│ (JavaScript) +└──────┬───────────────────┘ + │ + v +┌──────────────────┐ +│ Speichere in │ (NocoDB Create) +│ NocoDB │ +└──────────────────┘ + +Flow 2: Location API +┌──────────────────────┐ +│ Webhook - Location │ (GET /webhook/location) +│ API │ +└──────┬───────────────┘ + │ + v +┌──────────────────────┐ +│ Lade Daten aus │ (NocoDB Get All) +│ NocoDB │ +└──────┬───────────────┘ + │ + v +┌──────────────────────┐ +│ Format API Response │ (JavaScript) +└──────┬───────────────┘ + │ + v +┌──────────────────────┐ +│ JSON Response │ (CORS + JSON) +└──────────────────────┘ ``` -**API-Endpunkt**: -``` -Webhook - Location API (GET /webhook/location) - ↓ -Lade Daten für API (Shell: cat /tmp/n8n-locations.json) - ↓ -Format API Response (JS: JSON strukturieren) - ↓ -JSON Response (CORS + JSON zurückgeben) +### Flow 1: MQTT Location Capture (Details) + +**MQTT Trigger:** +- Subscribed auf Topic: `owntracks/#` +- Empfängt alle OwnTracks-Messages +- Keine Filter auf Trigger-Ebene + +**MQTT Location verarbeiten (JavaScript):** +```javascript +// Wichtige Schritte: +1. Parse JSON aus message-Feld +2. Validiere lat, lon, tst (erforderlich) +3. Konvertiere Unix-Timestamp → ISO 8601 +4. Extrahiere tid (Tracker ID) → username +5. Formatiere displayTime (de-DE, Europe/Berlin) +6. Packe Telemetrie in mqtt_data Objekt +7. Überspringe ungültige Nachrichten mit continue ``` -### tracker-db.json (NocoDB) +**Speichere in NocoDB:** +- Erstellt neuen Datensatz pro Location +- Mappt 12 Felder (inkl. battery, speed) +- Keine Duplikatsprüfung (alle Updates werden gespeichert) -**Standort-Erfassung**: -``` -Telegram Trigger - ↓ -Hat Location? (Filter) - ↓ -Location verarbeiten (JS: Daten extrahieren & formatieren) - ↓ -Speichere in NocoDB (NocoDB: Create Record) - ↓ -[Parallel] - ↓ -Hole letzten Eintrag (NocoDB: List Records, Limit 1, Sort desc) - ↓ -Zähle Einträge (NocoDB: Count) - ↓ -Merge (JS: Combine Results) - ↓ -Bereite Bestätigung vor (JS: Format Message) - ↓ -Telegram Bestätigung (Nachricht mit Stats & Link) +### Flow 2: Location API (Details) + +**Webhook - Location API:** +- HTTP GET auf `/location` +- CORS: `Access-Control-Allow-Origin: *` +- Keine Authentifizierung (öffentlich!) + +**Lade Daten aus NocoDB:** +- Holt ALLE Datensätze (`returnAll: true`) +- Keine Sortierung auf DB-Ebene +- Keine Pagination + +**Format API Response (JavaScript):** +```javascript +// Schritte: +1. Sammle alle Location-Objekte +2. Sortiere nach timestamp (neueste zuerst) +3. Wähle neuste als "current" +4. Baue Response-Struktur +5. Zähle total_points ``` -**API-Endpunkt**: -``` -Webhook - Location API (GET /webhook/location) - ↓ -Lade Daten aus NocoDB (NocoDB: List Records, Sort by timestamp desc) - ↓ -Format API Response (JS: JSON strukturieren) - ↓ -JSON Response (CORS + JSON zurückgeben) -``` - -### tracker-mqtt.json (MQTT/OwnTracks) - -**Standort-Erfassung** (vereinfachter Single-Path): -``` -MQTT Trigger (Topic: owntracks/#) - ↓ -Ist Location? (Filter: _type === "location") - ↓ -MQTT Location verarbeiten (JS: OwnTracks → NocoDB Schema Mapping) - ↓ -Speichere in NocoDB (NocoDB: Create Record) -``` - -**Keine separate Bestätigung** (MQTT ist unidirektional) - -**API-Endpunkt**: Shared mit tracker-db.json (gleiche NocoDB-Tabelle) +**JSON Response:** +- Content-Type: application/json +- CORS-Header gesetzt +- Keine Kompression ## Datenspeicherung & Schema -### tracker.json (Datei-basiert) +### NocoDB-Konfiguration -**Speicherung**: -- **Speicherort**: `/tmp/n8n-locations.json` -- **Format**: JSON-Array mit Location-Objekten -- **Maximale Einträge**: 100 (älteste werden automatisch entfernt) -- **Persistenz**: Überlebt n8n-Neustarts, aber nicht System-Neustarts (da `/tmp`) +**Aktuelle IDs im Workflow:** +- **Project ID**: `pdxl4cx4dbu9nxi` (muss angepasst werden!) +- **Table ID**: `m8pqj5ixgnnrzkg` (muss angepasst werden!) +- **Credential**: "NocoDB Token account" -**Empfehlung für Produktion**: -Ändere den Speicherort zu einem persistenten Pfad: +### Datenbank-Schema -In den Nodes **"Lade existierende Daten"** und **"Lade Daten für API"**: -```bash -cat /var/lib/n8n/locations.json 2>/dev/null || echo '[]' -``` +Vollständiges Schema mit Beispieldaten: -In Node **"Speichere in File"**: -```bash -echo '...' > /var/lib/n8n/locations.json -``` +| Feld | Typ | Beispielwert | Beschreibung | +|------|-----|--------------|--------------| +| `latitude` | Decimal | `48.1383784` | Breitengrad (WGS84) | +| `longitude` | Decimal | `11.4276172` | Längengrad (WGS84) | +| `timestamp` | DateTime | `2025-11-14T18:00:37.000Z` | UTC-Zeitstempel (ISO 8601) | +| `user_id` | Number | `0` | Immer 0 für MQTT-Geräte | +| `first_name` | Text | `"11"` | Tracker-ID (tid) | +| `last_name` | Text | `"fused"` | Location-Source | +| `username` | Text | `"11"` | Tracker-ID (gleich wie first_name) | +| `marker_label` | Text | `"11"` | Anzeigename für Karte | +| `display_time` | Text | `"14.11.2025, 19:00:37"` | Formatiert (de-DE) | +| `chat_id` | Number | `0` | Immer 0 für MQTT-Geräte | +| `battery` | Number | `73` | Batteriestatus (0-100%) | +| `speed` | Decimal | `0` | Geschwindigkeit in m/s | -### tracker-db.json & tracker-mqtt.json (NocoDB) +### OwnTracks-Feld-Mapping -**Speicherung**: -- **Backend**: NocoDB Datenbank -- **Project ID**: `pdxl4cx4dbu9nxi` (Beispiel - muss angepasst werden) -- **Table ID**: `m8pqj5ixgnnrzkg` (Beispiel - muss angepasst werden) -- **Maximale Einträge**: Unbegrenzt (Datenbank-basiert) -- **Persistenz**: Vollständig persistent -- **Shared Database**: Beide Workflows nutzen die gleiche Tabelle +| NocoDB-Feld | OwnTracks-Feld | Transformation | +|-------------|----------------|----------------| +| `latitude` | `lat` | Direkt | +| `longitude` | `lon` | Direkt | +| `timestamp` | `tst` | Unix → ISO 8601 | +| `user_id` | - | Konstant: `0` | +| `first_name` | `tid` | Tracker-ID | +| `last_name` | `source` | Location-Quelle | +| `username` | `tid` | Tracker-ID | +| `marker_label` | `tid` | Tracker-ID | +| `display_time` | `tst` | Formatiert (de-DE, Berlin) | +| `chat_id` | - | Konstant: `0` | +| `battery` | `batt` | Direkt | +| `speed` | `vel` | m/s (nicht konvertiert!) | -### Location-Objekt Schema - -**Alle Workflows** nutzen das gleiche Schema für Konsistenz: - -```json -{ - "latitude": 48.1351, // Decimal (Breitengrad) - "longitude": 11.5820, // Decimal (Längengrad) - "timestamp": "2025-11-14T10:30:00.000Z", // ISO 8601 DateTime - "user_id": 123456789, // Number (Telegram ID oder 0 für MQTT) - "first_name": "Max", // Text (Telegram: Vorname, MQTT: tracker ID) - "last_name": "Mustermann", // Text (Telegram: Nachname, MQTT: source) - "username": "maxmuster", // Text (Telegram: @username, MQTT: tracker ID) - "marker_label": "Max Mustermann", // Text (Anzeigename für Karte) - "display_time": "14.11.2025, 11:30:00", // Text (de-DE formatiert) - "chat_id": 123456789 // Number (Telegram Chat ID oder 0 für MQTT) -} -``` - -### Unterscheidung Telegram vs. MQTT - -In der Datenbank/API können Einträge anhand folgender Felder unterschieden werden: - -| Feld | Telegram | MQTT/OwnTracks | -|------|----------|----------------| -| `user_id` | Echte Telegram-User-ID (z.B. 123456789) | `0` | -| `chat_id` | Echte Telegram-Chat-ID (z.B. 123456789) | `0` | -| `first_name` | Telegram-Vorname (z.B. "Max") | Tracker-ID (z.B. "le") | -| `last_name` | Telegram-Nachname (z.B. "Mustermann") | Source (z.B. "fused") | -| `marker_label` | "Vorname Nachname" | "TID @ SSID" (z.B. "le @ HomeWifi") | - -### MQTT-spezifische Daten - -OwnTracks sendet zusätzliche Telemetrie-Daten, die **nicht** in der Datenbank gespeichert werden, aber im Node "MQTT Location verarbeiten" verfügbar sind: - -```json -{ - "acc": 10, // Genauigkeit in Metern - "alt": 520, // Höhe über Meeresspiegel - "batt": 85, // Batteriestatus (0-100%) - "vel": 5, // Geschwindigkeit (m/s) - "conn": "w", // Verbindungstyp (w=WiFi, m=Mobile) - "t": "u" // Trigger (u=User, t=Timer, etc.) -} -``` - -Diese Daten können bei Bedarf zum Schema hinzugefügt werden (erfordert Anpassung der NocoDB-Tabelle und Workflows) +**Nicht gespeicherte OwnTracks-Felder:** +- `acc` - Genauigkeit (Meter) +- `alt` - Höhe (Meter) +- `cog` - Kurs über Grund +- `conn` - Verbindungstyp (w/m) +- `_id` - Device Identifier ## Anpassungen & Customization -### Anzahl gespeicherter Standorte ändern (nur tracker.json) +### Neues Gerät hinzufügen -Im Node **"Merge mit History"** die Limit-Logik anpassen: +**Schritt 1: OwnTracks-App konfigurieren** +- Setze Tracker ID (tid) auf eindeutige ID, z.B. "12" +- Konfiguriere MQTT-Verbindung wie oben beschrieben + +**Schritt 2: index.html anpassen (Zeilen 142-152)** ```javascript -// Aktuell: 100 Einträge -if (locations.length > 100) { - locations = locations.slice(0, 100); -} +const DEVICE_NAMES = { + '10': 'Joachim Pixel', + '11': 'Huawei Smartphone', + '12': 'Neues Gerät' // HINZUFÜGEN +}; -// Ändern zu z.B. 500 Einträge: -if (locations.length > 500) { - locations = locations.slice(0, 500); -} +const DEVICE_COLORS = { + '10': '#e74c3c', + '11': '#3498db', + '12': '#2ecc71', // HINZUFÜGEN (Grün) + 'default': '#95a5a6' +}; ``` -**Hinweis**: NocoDB-Workflows haben kein Client-Side-Limit. +**Farb-Vorschläge:** +- `#e74c3c` - Rot +- `#3498db` - Blau +- `#2ecc71` - Grün +- `#f39c12` - Orange +- `#9b59b6` - Lila +- `#1abc9c` - Türkis -### Datumsformat ändern +**Schritt 3: Testen** +- Sende Location von neuem Gerät +- Prüfe Web-Oberfläche → Gerät sollte im Dropdown erscheinen +- Marker sollte in konfigurierter Farbe erscheinen -Im Node **"Location verarbeiten"** (Telegram) oder **"MQTT Location verarbeiten"** (MQTT) das Locale anpassen: +### Zeitzone ändern + +**In n8n-Workflow, Node "MQTT Location verarbeiten" (Zeile 124):** ```javascript -// Aktuell: Deutsch (de-DE) -const displayTime = new Date(messageDate * 1000).toLocaleString('de-DE'); +// Aktuell: Berlin-Zeit +const displayTime = new Date(timestampMs).toLocaleString('de-DE', { + timeZone: 'Europe/Berlin' +}); -// Ändern zu z.B. Englisch (en-US): -const displayTime = new Date(messageDate * 1000).toLocaleString('en-US'); +// Ändern zu New York: +const displayTime = new Date(timestampMs).toLocaleString('en-US', { + timeZone: 'America/New_York' +}); -// Oder eigenes Format: -const displayTime = new Date(messageDate * 1000).toLocaleString('de-DE', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' +// Ändern zu UTC: +const displayTime = new Date(timestampMs).toISOString(); + +// Eigenes Format: +const displayTime = new Date(timestampMs).toLocaleString('de-DE', { + timeZone: 'Europe/Berlin', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' }); ``` -### CORS-Beschränkung (Sicherheit) +### Standard-Zeitfilter ändern -Im Node **"Webhook - Location API"** unter **Options → Response Headers**: +**In index.html (Zeile 125):** -```javascript -// Aktuell (unsicher für Produktion): Alle Origins erlaubt -"Access-Control-Allow-Origin": "*" +```html + + -// Besser für Produktion: Spezifische Domain -"Access-Control-Allow-Origin": "https://deine-domain.de" - -// Oder mehrere Domains (erfordert Logik im Node): -// const allowedOrigins = ['https://domain1.de', 'https://domain2.de']; -// const origin = request.headers.origin; -// return allowedOrigins.includes(origin) ? origin : allowedOrigins[0]; + + + ``` -### Auto-Refresh Intervall anpassen +### Auto-Refresh-Intervall anpassen -In **index.html** oder **index_owntrack.html**: +**In index.html (Zeile 419):** ```javascript // Aktuell: 5 Sekunden (5000ms) refreshInterval = setInterval(loadLocations, 5000); -// Ändern zu z.B. 10 Sekunden: +// Ändern zu 10 Sekunden: refreshInterval = setInterval(loadLocations, 10000); -// Oder 30 Sekunden: -refreshInterval = setInterval(loadLocations, 30000); +// Ändern zu 1 Minute: +refreshInterval = setInterval(loadLocations, 60000); ``` -### MQTT Topic ändern +### CORS einschränken (Sicherheit!) -Im Node **"MQTT Trigger"** (tracker-mqtt.json): +**In n8n-Workflow, Node "JSON Response" (Zeile 67):** + +```json +// Aktuell (unsicher): +{ + "name": "Access-Control-Allow-Origin", + "value": "*" +} + +// Ändern zu spezifischer Domain: +{ + "name": "Access-Control-Allow-Origin", + "value": "https://web.example.com" +} +``` + +### Weitere NocoDB-Felder speichern + +**Beispiel: Genauigkeit (accuracy) und Höhe (altitude) hinzufügen** + +**Schritt 1: NocoDB-Spalten erstellen** +- `accuracy` (Number) +- `altitude` (Number) + +**Schritt 2: Workflow-Node "MQTT Location verarbeiten" anpassen:** + +```javascript +// In mqtt_data Objekt ergänzen: +mqtt_data: { + accuracy: mqttData.acc, + altitude: mqttData.alt, + battery: mqttData.batt, + velocity: mqttData.vel, + course: mqttData.cog, + connection: mqttData.conn, + device_id: mqttData._id +} +``` + +**Schritt 3: Node "Speichere in NocoDB" anpassen:** + +Füge in `fieldsUi.fieldValues` hinzu: +```json +{ + "fieldName": "accuracy", + "fieldValue": "={{ $json.mqtt_data.accuracy }}" +}, +{ + "fieldName": "altitude", + "fieldValue": "={{ $json.mqtt_data.altitude }}" +} +``` + +**Schritt 4: index.html Popups erweitern (Zeile 320):** + +```javascript +// Nach Speed-Anzeige hinzufügen: +if (loc.accuracy !== undefined && loc.accuracy !== null) { + popupContent += `
📍 Genauigkeit: ${loc.accuracy}m`; +} + +if (loc.altitude !== undefined && loc.altitude !== null) { + popupContent += `
⛰️ Höhe: ${loc.altitude}m`; +} +``` + +### MQTT-Topic einschränken + +**In n8n-Workflow, Node "MQTT Trigger" (Zeile 104):** ```javascript // Aktuell: Alle OwnTracks-Topics -Topic: owntracks/# +topics: "owntracks/#" -// Ändern zu spezifischem User/Device: -Topic: owntracks/joachim/phone +// Nur spezifischer Benutzer: +topics: "owntracks/joachim/#" -// Oder eigene Topic-Struktur: -Topic: location/+/+ // location/user/device -``` +// Nur spezifisches Gerät: +topics: "owntracks/joachim/pixel" -Passe auch den Filter-Node **"Ist Location?"** entsprechend an. - -### NocoDB Tabellen-Felder erweitern - -Um MQTT-Telemetrie-Daten zu speichern: - -1. **In NocoDB**: Füge neue Spalten hinzu: - - `battery` (Number) - - `speed` (Decimal) - - `accuracy` (Number) - - `altitude` (Number) - -2. **Im Workflow** (Node "MQTT Location verarbeiten"): - ```javascript - // Füge zu locationData hinzu: - battery: json.batt || null, - speed: json.vel || null, - accuracy: json.acc || null, - altitude: json.alt || null - ``` - -3. **In index_owntrack.html**: Daten sind bereits ausgelesen (Zeilen 137-145) - -### Kartenebene Standardauswahl ändern - -In **index.html**: - -```javascript -// Aktuell: Standard (OSM) -let currentLayer = mapLayers.standard; - -// Ändern zu z.B. Satellit: -let currentLayer = mapLayers.satellite; - -// Und Dropdown synchronisieren: -document.getElementById('mapLayerSelect').value = 'satellite'; +// Mehrere Topics: +topics: "owntracks/joachim/#,owntracks/lisa/#" ``` ## Sicherheitshinweise -### Kritisch (vor Produktionseinsatz beheben!) +### Kritisch (sofort beheben!) -1. **API-Authentifizierung fehlt**: - - Der `/location` Endpunkt ist **öffentlich ohne Authentifizierung** zugänglich - - Jeder kann Standortdaten abrufen, wenn er die URL kennt - - **Empfehlung**: Implementiere API-Key-Authentifizierung in n8n oder nutze einen Reverse-Proxy mit Auth +**1. API ohne Authentifizierung** +- ⚠️ **Problem**: Jeder kann Standortdaten abrufen, wenn er die URL kennt +- ⚠️ **Risiko**: DSGVO-Verstoß, Privatsphäre-Verletzung +- ✅ **Lösung**: + - Implementiere API-Key-Authentifizierung in n8n + - Oder nutze Reverse-Proxy mit Basic Auth + - Oder beschränke Zugriff per IP-Whitelist -2. **CORS für alle Origins offen**: - - `Access-Control-Allow-Origin: *` erlaubt Zugriff von jeder Domain - - **Risiko**: Cross-Site-Scripting (XSS), Datenabfluss - - **Empfehlung**: Beschränke auf deine spezifische Domain (siehe Anpassungen) +**2. CORS offen für alle Domains** +- ⚠️ **Problem**: `Access-Control-Allow-Origin: *` +- ⚠️ **Risiko**: XSS-Angriffe, unautorisierten Zugriff +- ✅ **Lösung**: Beschränke auf deine Domain (siehe "CORS einschränken") -3. **Standortdaten sind hochsensibel (DSGVO)**: - - Personenbezogene Daten (Name, User-ID, exakte Koordinaten) - - **Pflichten**: Informationspflicht, Einwilligung, Löschkonzept - - **Empfehlung**: - - Hole explizite Einwilligung von Nutzern ein - - Implementiere automatische Löschung alter Daten (z.B. >30 Tage) - - Dokumentiere Datenschutzmaßnahmen +**3. DSGVO-Compliance** +- ⚠️ **Problem**: Personenbezogene Standortdaten ohne Einwilligung/Löschkonzept +- ⚠️ **Pflichten**: Informationspflicht, Einwilligung, Auskunftsrecht, Löschung +- ✅ **Lösung**: + - Hole explizite Einwilligung von Nutzern ein + - Implementiere automatische Löschung alter Daten (z.B. >30 Tage) + - Dokumentiere Datenschutzmaßnahmen + - Stelle Löschfunktion bereit -### Wichtig (empfohlene Sicherheitsmaßnahmen) +### Wichtig (empfohlen) -4. **Credentials-Sicherheit**: - - **Telegram-Bot-Token**: Niemals in Code oder Logs speichern - - **NocoDB-Token**: Nutze Read-Only-Token für API-Endpunkt (wenn möglich) - - **MQTT-Credentials**: Nutze TLS-Verschlüsselung (Port 8883) +**4. MQTT ohne TLS** +- ⚠️ **Problem**: Unverschlüsselte Übertragung auf Port 1883 +- ⚠️ **Risiko**: Standortdaten können abgefangen werden +- ✅ **Lösung**: + - Aktiviere TLS in Mosquitto (Port 8883) + - Konfiguriere OwnTracks mit TLS -5. **File-basierte Speicherung** (tracker.json): - - `/tmp` Verzeichnis ist evtl. für andere Benutzer lesbar - - **Empfehlung**: Setze Dateiberechtigungen (`chmod 600`) - - Besser: Nutze NocoDB-Variante für Produktion +**5. Keine Rate-Limiting** +- ⚠️ **Problem**: API kann unbegrenzt oft abgerufen werden +- ⚠️ **Risiko**: DoS-Angriff, Server-Überlastung +- ✅ **Lösung**: Implementiere Rate-Limiting (z.B. via nginx) -6. **Rate Limiting fehlt**: - - API kann beliebig oft abgerufen werden - - **Risiko**: DoS-Angriff, Server-Überlastung - - **Empfehlung**: Implementiere Rate Limiting (z.B. via nginx) +**6. NocoDB-Token zu weitreichend** +- ⚠️ **Problem**: Token hat möglicherweise Schreibrechte für API-Endpunkt +- ⚠️ **Risiko**: Datenmanipulation +- ✅ **Lösung**: Nutze separaten Read-Only-Token für API-Endpunkt (falls möglich) ### Best Practices -- **HTTPS erzwingen**: Stelle sicher, dass n8n-Webhooks nur über HTTPS erreichbar sind +- **HTTPS erzwingen**: n8n-Webhooks nur über HTTPS erreichbar machen - **Monitoring**: Überwache ungewöhnliche API-Zugriffe - **Backup**: Sichere NocoDB-Datenbank regelmäßig -- **Updates**: Halte n8n, NocoDB und alle Dependencies aktuell +- **Updates**: Halte n8n, NocoDB, Mosquitto und Dependencies aktuell +- **Secrets**: Speichere Credentials nur in n8n Credential Store, nicht im Code +- **Logging**: Aktiviere Audit-Logging für Zugriffe ## Fehlerbehebung -### Telegram-Bot antwortet nicht - -**Symptome**: Standort wird gesendet, aber keine Bestätigung - -**Lösungen**: -1. Prüfe, ob Workflow aktiv ist (grüner "Active"-Toggle in n8n) -2. Prüfe Telegram-Credentials: - ```bash - # In n8n: Credentials → Telegram → Test Connection - ``` -3. Prüfe Workflow-Execution-Historie: - - n8n → Workflows → tracker → Executions - - Suche nach Fehlermeldungen (rot markiert) -4. Prüfe Telegram-Bot-Webhook: - ```bash - curl https://api.telegram.org/bot/getWebhookInfo - ``` - -### API gibt leere/fehlerhafte Daten zurück - -**Symptome**: API antwortet mit `[]`, `null` oder HTTP 500 - -**Lösungen**: - -**Für tracker.json (Datei-basiert)**: -1. Prüfe, ob Datei existiert: - ```bash - ls -la /tmp/n8n-locations.json - ``` -2. Prüfe Dateiinhalt: - ```bash - cat /tmp/n8n-locations.json | jq . - ``` -3. Prüfe Berechtigungen: - ```bash - # n8n-User muss lesen können - chmod 644 /tmp/n8n-locations.json - ``` - -**Für tracker-db.json/tracker-mqtt.json (NocoDB)**: -1. Teste NocoDB-Verbindung in n8n (Credentials → Test) -2. Prüfe Project/Table IDs im Workflow -3. Prüfe NocoDB-API direkt: - ```bash - curl -H "xc-token: YOUR_TOKEN" \ - https://nocodb.example.com/api/v1/db/data/v1/PROJECT_ID/TABLE_ID - ``` - -### MQTT-Daten kommen nicht an (tracker-mqtt.json) +### MQTT-Daten kommen nicht an **Symptome**: OwnTracks sendet, aber nichts in NocoDB gespeichert -**Lösungen**: -1. Teste MQTT-Broker-Verbindung: +**Lösungen:** + +1. **MQTT-Broker testen:** ```bash mosquitto_sub -h broker.example.com -p 1883 -u user -P pass -t 'owntracks/#' -v ``` -2. Prüfe OwnTracks-Konfiguration: + Sollte Nachrichten anzeigen, wenn OwnTracks sendet. + +2. **OwnTracks-Konfiguration prüfen:** - Mode: MQTT (nicht HTTP!) - Topic: `owntracks/USER/DEVICE` - - TLS: Nur wenn Broker TLS nutzt -3. Prüfe n8n MQTT-Node: - - Credentials korrekt - - Topic-Pattern passt (`owntracks/#`) -4. Prüfe Workflow-Filter: - - Node "Ist Location?" muss `_type: "location"` filtern -5. Debug mit Workflow-Execution: - - Trigger manuell mit Test-Payload - ```json - { - "_type": "location", - "lat": 48.1351, - "lon": 11.5820, - "tid": "le", - "tst": 1731582600 - } + - Verbindungsstatus in App prüfen + - Test-Nachricht senden (Publish Button) + +3. **n8n MQTT-Node prüfen:** + - Credentials korrekt? + - Topic-Pattern passt? (`owntracks/#`) + - Workflow ist aktiviert? + +4. **n8n Execution History prüfen:** + - Workflows → n8n-tracker → Executions + - Gibt es Executions? + - Gibt es Fehler (rot markiert)? + +5. **Debug mit manuellem Test:** + ```bash + # Sende Test-Nachricht per mosquitto_pub + mosquitto_pub -h broker.example.com -p 1883 -u user -P pass \ + -t 'owntracks/test/device' \ + -m '{"_type":"location","lat":48.1351,"lon":11.5820,"tid":"10","tst":1700000000,"batt":85,"vel":5}' ``` +### API gibt leere Daten zurück + +**Symptome**: API antwortet mit `{"history": []}` oder `"current": null` + +**Lösungen:** + +1. **NocoDB-Verbindung testen:** + - In n8n: Credentials → NocoDB → Test Connection + - Sollte grüner Haken erscheinen + +2. **NocoDB direkt testen:** + ```bash + curl -H "xc-token: YOUR_TOKEN" \ + "https://nocodb.example.com/api/v1/db/data/v1/PROJECT_ID/TABLE_ID" + ``` + Sollte JSON mit Daten zurückgeben. + +3. **Project/Table IDs prüfen:** + - Öffne NocoDB-Tabelle im Browser + - URL enthält die IDs: `/nc/PROJECT_ID/TABLE_ID` + - Vergleiche mit IDs in n8n-Workflow + +4. **Daten in NocoDB vorhanden?** + - Öffne Tabelle in NocoDB + - Sind Einträge vorhanden? + - Wenn nicht: Problem liegt bei MQTT-Erfassung (siehe oben) + ### Web-Oberfläche zeigt keine Karte **Symptome**: Weiße Seite, Karte lädt nicht, Marker fehlen -**Lösungen**: -1. Prüfe Browser-Console (F12 → Console): - - CORS-Fehler? → Siehe Sicherheitshinweise - - 404 auf Leaflet.js? → CDN-Problem, lokale Kopie nutzen +**Lösungen:** + +1. **Browser-Console prüfen (F12 → Console):** + - CORS-Fehler? → API-CORS-Header prüfen + - 404 auf Leaflet.js? → CDN-Problem (lokale Kopie nutzen) - API-Fehler? → Siehe "API gibt leere Daten zurück" -2. Prüfe API-URL in HTML: - ```javascript - // index.html Zeile 175 - // index_owntrack.html Zeile 85 - const API_URL = 'https://...'; // Muss erreichbar sein! - ``` -3. Teste API direkt im Browser: - ``` - https://deine-n8n-instanz.de/webhook/location - ``` - Sollte JSON zurückgeben, nicht HTML/Fehlerseite -4. Prüfe Netzwerk-Tab (F12 → Network): - - Status 200 für API-Request? + - JavaScript-Fehler? → Code-Syntax prüfen + +2. **API-URL prüfen:** + - In index.html Zeile 178: `const API_URL = '...'` + - URL muss erreichbar sein + - Test im Browser: URL direkt aufrufen → Sollte JSON zurückgeben + +3. **Netzwerk-Tab prüfen (F12 → Network):** + - Request zu API wird gesendet? + - Status 200 OK? + - Response enthält Daten? - CORS-Header vorhanden? +4. **Leaflet.js CDN erreichbar?** + - Prüfe ob `https://unpkg.com/leaflet@1.9.4/dist/leaflet.js` geladen wird + - Falls CDN-Problem: Nutze lokale Kopie + ### Koordinaten sind falsch/vertauscht **Symptome**: Marker erscheinen im Meer, falsche Position -**Lösungen**: -1. Prüfe Reihenfolge: **Latitude (Breitengrad) kommt vor Longitude (Längengrad)** - - Richtig: `[48.1351, 11.5820]` (lat, lon) - - Falsch: `[11.5820, 48.1351]` (lon, lat) -2. Prüfe MQTT-Mapping (nur tracker-mqtt.json): - - Node "MQTT Location verarbeiten" - - `latitude: json.lat` (nicht `json.lon`!) -3. Prüfe String-Parsing: +**Lösungen:** + +1. **Reihenfolge prüfen:** + - Leaflet erwartet: `[latitude, longitude]` + - NICHT: `[longitude, latitude]` + - OwnTracks sendet korrekt: `lat`, `lon` + +2. **Daten in NocoDB prüfen:** + - Öffne Tabelle + - Ist `latitude` der Breitengrad (z.B. 48.x)? + - Ist `longitude` der Längengrad (z.B. 11.x)? + - Für München: ca. 48°N, 11°O + +3. **JavaScript-Code prüfen:** ```javascript - // Koordinaten müssen Numbers sein, nicht Strings! - const lat = parseFloat(loc.latitude); // Gut - const lat = loc.latitude; // Schlecht, wenn String + // RICHTIG: + const lat = parseFloat(loc.latitude); + const lon = parseFloat(loc.longitude); + L.marker([lat, lon]) + + // FALSCH: + L.marker([lon, lat]) // Vertauscht! ``` -### Standorte verschwinden nach System-Neustart (tracker.json) +### Geräte-Filter zeigt nicht alle Geräte -**Symptome**: Nach Neustart des Servers sind alle Standorte weg +**Symptome**: Dropdown zeigt "Alle Geräte" aber keine einzelnen Geräte -**Ursache**: `/tmp` wird bei System-Neustart geleert +**Lösungen:** -**Lösungen**: -1. **Kurzfristig**: Nutze persistenten Pfad (siehe "Datenspeicherung & Schema") -2. **Langfristig**: Wechsele zu tracker-db.json (NocoDB) +1. **MQTT-Daten vorhanden?** + - API aufrufen und prüfen: Gibt es Einträge mit `user_id: 0`? + - Wenn nicht: Keine MQTT-Daten in Datenbank + +2. **username-Feld befüllt?** + - In NocoDB prüfen: Ist `username` gesetzt? + - Sollte gleich wie `first_name` sein (tid) + +3. **JavaScript-Console prüfen:** + ```javascript + // In Browser-Console (F12): + console.log(allData.history.filter(loc => loc.user_id == 0)); + ``` + Sollte MQTT-Einträge zeigen. + +4. **Filter-Code prüfen (index.html Zeile 267):** + ```javascript + let filteredData = allData.history.filter(loc => loc.user_id == 0); + ``` + Muss MQTT-Daten filtern. + +### Geschwindigkeit wird nicht angezeigt + +**Symptome**: Popup zeigt keine Geschwindigkeit, obwohl OwnTracks sendet + +**Lösungen:** + +1. **OwnTracks sendet velocity?** + - Prüfe MQTT-Nachricht (mosquitto_sub) + - Sollte `vel` Feld enthalten + +2. **NocoDB-Feld `speed` vorhanden?** + - Tabellen-Schema prüfen + - Spalte `speed` (Decimal) muss existieren + +3. **Workflow speichert speed?** + - Node "Speichere in NocoDB" prüfen + - Mapping: `fieldName: "speed"`, `fieldValue: "={{ $json.mqtt_data.velocity }}"` + +4. **Null-Werte prüfen:** + - Nicht alle OwnTracks-Messages enthalten `vel` + - Code prüft auf `!== null` (index.html Zeile 328) + +### Batteriestatus zeigt 0% oder fehlt + +**Symptome**: Batterie wird als 0% angezeigt oder fehlt im Popup + +**Lösungen:** + +1. **OwnTracks sendet battery?** + - Android/iOS unterscheiden sich + - Manche Geräte senden kein `batt` Feld + - Prüfe MQTT-Nachricht + +2. **Berechtigungen in OwnTracks:** + - Android: Batterie-Optimierung deaktivieren + - iOS: Standortfreigabe "Immer" setzen + +3. **NocoDB-Wert prüfen:** + - Tabelle öffnen + - Ist `battery` befüllt? + - Typ Number (nicht Text!) ## Repository-Inhalte -| Datei | Beschreibung | Typ | -|-------|--------------|-----| -| `tracker.json` | Telegram + Datei-Speicherung | n8n Workflow | -| `tracker-db.json` | Telegram + NocoDB | n8n Workflow | -| `tracker-mqtt.json` | MQTT/OwnTracks + NocoDB | n8n Workflow | -| `index.html` | Erweiterte Multi-Source Web-UI | HTML/JavaScript | -| `index_owntrack.html` | MQTT-fokussierte Web-UI | HTML/JavaScript | -| `locations-example.csv` | Beispieldaten für Tests | CSV | -| `README.md` | Diese Dokumentation | Markdown | -| `CLAUDE.md` | Technische Architektur-Doku | Markdown | +| Datei | Beschreibung | +|-------|--------------| +| `n8n-tracker.json` | n8n-Workflow für MQTT-Erfassung und API | +| `index.html` | Web-Oberfläche mit Leaflet.js | +| `database-example.csv` | Beispiel-Datenexport aus NocoDB | +| `README.md` | Diese Dokumentation | +| `CLAUDE.md` | Technische Architektur-Dokumentation | ## Lizenz @@ -799,8 +950,9 @@ Dieses Projekt steht unter der **MIT-Lizenz** zur freien Verfügung. - [ ] API-Authentifizierung (API-Key, JWT) - [ ] Automatische Datenlöschung (DSGVO-Compliance) - [ ] Geofencing / Location-Alerts -- [ ] Multi-Tenant-Support (mehrere Bots) +- [ ] Multi-User-Support mit Zugriffsrechten - [ ] Erweiterte Statistiken (Distanz, Durchschnittsgeschwindigkeit) -- [ ] Export-Funktion (GPX, KML) +- [ ] Export-Funktion (GPX, KML, CSV) - [ ] Push-Notifications bei Location-Updates - [ ] Offline-Support für Web-UI (PWA) +- [ ] Mobile App (React Native / Flutter) diff --git a/database-example.csv b/database-example.csv new file mode 100644 index 0000000..701e88d --- /dev/null +++ b/database-example.csv @@ -0,0 +1,12 @@ +latitude,longitude,timestamp,user_id,first_name,last_name,username,marker_label,display_time,chat_id,battery,speed +48.1383784,11.4276172,2025-11-14T18:00:37.000Z,0,11,fused,11,11,"14.11.2025, 19:00:37",0,73,0 +48.1383761,11.4276122,2025-11-14T18:00:43.000Z,0,11,fused,11,11,"14.11.2025, 19:00:43",0,73,0 +48.1383761,11.4276122,2025-11-14T18:00:43.000Z,0,11,fused,11,11,"14.11.2025, 19:00:43",0,73,0 +48.1383739,11.4276184,2025-11-14T18:00:49.000Z,0,11,fused,11,11,"14.11.2025, 19:00:49",0,73,0 +48.1383635,11.4276113,2025-11-14T18:00:56.000Z,0,11,fused,11,11,"14.11.2025, 19:00:56",0,73,0 +48.1383631,11.4276126,2025-11-14T18:00:58.000Z,0,11,fused,11,11,"14.11.2025, 19:00:58",0,73,0 +48.1383735,11.4276116,2025-11-14T18:01:08.000Z,0,11,fused,11,11,"14.11.2025, 19:01:08",0,73,0 +48.1383595,11.4276089,2025-11-14T18:01:14.000Z,0,11,fused,11,11,"14.11.2025, 19:01:14",0,73,0 +48.1383581,11.4275939,2025-11-14T18:01:20.000Z,0,11,fused,11,11,"14.11.2025, 19:01:20",0,73,0 +48.1383497,11.4276117,2025-11-14T18:01:26.000Z,0,11,fused,11,11,"14.11.2025, 19:01:26",0,73,0 + diff --git a/index_owntrack.html b/index_owntrack.html deleted file mode 100644 index 80042c1..0000000 --- a/index_owntrack.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - Location Test - - - - -
-
-

📍 Location Tracker

-
Lade...
- -
- - - - - - diff --git a/locations-example.csv b/locations-example.csv deleted file mode 100644 index 1c2188b..0000000 --- a/locations-example.csv +++ /dev/null @@ -1,9 +0,0 @@ -latitude,longitude,timestamp,user_id,first_name,last_name,username,marker_label,display_time,chat_id -48.1351,11.5820,2025-01-15T10:30:00.000Z,123456789,Max,Mustermann,maxmustermann,Max Mustermann,"15.1.2025, 11:30:00",987654321 -48.1375,11.5750,2025-01-15T11:45:00.000Z,123456789,Max,Mustermann,maxmustermann,Max Mustermann,"15.1.2025, 12:45:00",987654321 -48.1400,11.5680,2025-01-15T13:15:00.000Z,123456789,Max,Mustermann,maxmustermann,Max Mustermann,"15.1.2025, 14:15:00",987654321 -48.1425,11.5610,2025-01-15T14:30:00.000Z,123456789,Max,Mustermann,maxmustermann,Max Mustermann,"15.1.2025, 15:30:00",987654321 -48.1450,11.5540,2025-01-15T15:45:00.000Z,123456789,Max,Mustermann,maxmustermann,Max Mustermann,"15.1.2025, 16:45:00",987654321 -48.1475,11.5470,2025-01-15T17:00:00.000Z,987654321,Anna,Schmidt,annaschmidt,Anna Schmidt,"15.1.2025, 18:00:00",123456789 -48.1500,11.5400,2025-01-15T18:15:00.000Z,987654321,Anna,Schmidt,annaschmidt,Anna Schmidt,"15.1.2025, 19:15:00",123456789 -48.1525,11.5330,2025-01-15T19:30:00.000Z,987654321,Anna,Schmidt,annaschmidt,Anna Schmidt,"15.1.2025, 20:30:00",123456789 diff --git a/n8n-tracker.json b/n8n-tracker.json new file mode 100644 index 0000000..233fd55 --- /dev/null +++ b/n8n-tracker.json @@ -0,0 +1,290 @@ +{ + "name": "Telegram Location Tracker - NocoDB", + "nodes": [ + { + "parameters": { + "path": "location", + "responseMode": "lastNode", + "options": { + "allowedOrigins": "*" + } + }, + "id": "116b19ae-d634-48cd-aae8-3d9a4dbfebf3", + "name": "Webhook - Location API", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 256, + 336 + ], + "webhookId": "location-api-endpoint" + }, + { + "parameters": { + "authentication": "nocoDbApiToken", + "operation": "getAll", + "projectId": "pdxl4cx4dbu9nxi", + "table": "m8pqj5ixgnnrzkg", + "returnAll": true, + "options": {} + }, + "id": "e9b8577e-b736-4f3f-98e9-1309f5373f43", + "name": "Lade Daten aus NocoDB", + "type": "n8n-nodes-base.nocoDb", + "typeVersion": 2, + "position": [ + 448, + 336 + ], + "credentials": { + "nocoDbApiToken": { + "id": "T9XuGr6CJD2W2BPO", + "name": "NocoDB Token account" + } + } + }, + { + "parameters": { + "jsCode": "// Baue API Response aus NocoDB-Daten\nconst locations = $input.all().map(item => item.json);\n\n// Sortiere nach timestamp (neueste zuerst)\nlocations.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));\n\nconst current = locations.length > 0 ? locations[0] : null;\n\nreturn [{\n json: {\n success: true,\n current: current,\n history: locations,\n total_points: locations.length,\n last_updated: current ? current.timestamp : null\n }\n}];" + }, + "id": "771d0ec3-2248-4159-b03c-9382f60f765f", + "name": "Format API Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 640, + 336 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ $json }}", + "options": { + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + } + } + }, + "id": "2d096d00-8844-473a-91f0-5a9962b5ac28", + "name": "JSON Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 816, + 336 + ] + }, + { + "parameters": { + "height": 544, + "width": 1104, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 0, + 0 + ], + "typeVersion": 1, + "id": "388be06b-01ba-4e7b-8e5a-5be25fa4efa0", + "name": "Sticky Note" + }, + { + "parameters": { + "topics": "owntracks/#", + "options": {} + }, + "id": "cadaab24-22b6-4c44-bee3-2a10f7b4a68c", + "name": "MQTT Trigger", + "type": "n8n-nodes-base.mqttTrigger", + "typeVersion": 1, + "position": [ + 336, + 128 + ], + "credentials": { + "mqtt": { + "id": "L07VVR2BDfDda6Zo", + "name": "MQTT account" + } + } + }, + { + "parameters": { + "jsCode": "// Extrahiere MQTT Location-Daten für NocoDB\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n // Parse den JSON-String aus dem message-Feld\n const mqttData = JSON.parse(item.json.message);\n \n // Validiere erforderliche Felder\n if (!mqttData.lat || !mqttData.lon || !mqttData.tst) {\n console.log('Fehlende Felder:', mqttData);\n continue;\n }\n \n // Timestamp (tst = Unix timestamp in seconds)\n const timestampMs = mqttData.tst * 1000;\n const timestamp = new Date(timestampMs).toISOString();\n const displayTime = new Date(timestampMs).toLocaleString('de-DE', { timeZone: 'Europe/Berlin' });\n \n // Erstelle marker_label aus tid (tracker ID)\n const trackerLabel = mqttData.tid || 'Unknown';\n \n results.push({\n json: {\n latitude: mqttData.lat,\n longitude: mqttData.lon,\n timestamp: timestamp,\n user_id: 0,\n first_name: trackerLabel,\n last_name: mqttData.source || 'mqtt',\n username: mqttData.tid || '',\n marker_label: trackerLabel,\n display_time: displayTime,\n chat_id: 0,\n // Optional: Zusätzliche MQTT-Daten\n mqtt_data: {\n accuracy: mqttData.acc,\n altitude: mqttData.alt,\n battery: mqttData.batt,\n velocity: mqttData.vel,\n course: mqttData.cog,\n connection: mqttData.conn,\n device_id: mqttData._id\n }\n }\n });\n } catch (error) {\n console.error('Parse-Fehler:', error.message);\n continue;\n }\n}\n\nreturn results;" + }, + "id": "cc71dfb1-3f18-4e57-af6d-df952615ff46", + "name": "MQTT Location verarbeiten", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 592, + 128 + ], + "alwaysOutputData": true + }, + { + "parameters": { + "authentication": "nocoDbApiToken", + "operation": "create", + "projectId": "pdxl4cx4dbu9nxi", + "table": "m8pqj5ixgnnrzkg", + "fieldsUi": { + "fieldValues": [ + { + "fieldName": "latitude", + "fieldValue": "={{ $json.latitude }}" + }, + { + "fieldName": "longitude", + "fieldValue": "={{ $json.longitude }}" + }, + { + "fieldName": "timestamp", + "fieldValue": "={{ $json.timestamp }}" + }, + { + "fieldName": "user_id", + "fieldValue": "={{ $json.user_id }}" + }, + { + "fieldName": "first_name", + "fieldValue": "={{ $json.first_name }}" + }, + { + "fieldName": "last_name", + "fieldValue": "={{ $json.last_name }}" + }, + { + "fieldName": "username", + "fieldValue": "={{ $json.username }}" + }, + { + "fieldName": "marker_label", + "fieldValue": "={{ $json.marker_label }}" + }, + { + "fieldName": "display_time", + "fieldValue": "={{ $json.display_time }}" + }, + { + "fieldName": "chat_id", + "fieldValue": "={{ $json.chat_id }}" + }, + { + "fieldName": "battery", + "fieldValue": "={{ $json.mqtt_data.battery }}" + }, + { + "fieldName": "speed", + "fieldValue": "={{ $json.mqtt_data.velocity }}" + } + ] + } + }, + "id": "ea8305b8-3d7f-4b9a-8ba6-04406d21815f", + "name": "Speichere in NocoDB", + "type": "n8n-nodes-base.nocoDb", + "typeVersion": 2, + "position": [ + 832, + 128 + ], + "credentials": { + "nocoDbApiToken": { + "id": "T9XuGr6CJD2W2BPO", + "name": "NocoDB Token account" + } + } + } + ], + "pinData": {}, + "connections": { + "Webhook - Location API": { + "main": [ + [ + { + "node": "Lade Daten aus NocoDB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Lade Daten aus NocoDB": { + "main": [ + [ + { + "node": "Format API Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format API Response": { + "main": [ + [ + { + "node": "JSON Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "MQTT Trigger": { + "main": [ + [ + { + "node": "MQTT Location verarbeiten", + "type": "main", + "index": 0 + } + ] + ] + }, + "MQTT Location verarbeiten": { + "main": [ + [ + { + "node": "Speichere in NocoDB", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false, + "errorWorkflow": "0bBZzSE6SUzVsif5" + }, + "versionId": "de17706a-a0ea-42ce-a069-dd09dce421d2", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "2f7fd37175cefa82de44e06b8af9ab9c01e7956018045d6efc4f7bf1588a41eb" + }, + "id": "6P6dKqi4IKcJ521m", + "tags": [ + { + "updatedAt": "2025-11-14T13:41:54.886Z", + "createdAt": "2025-11-14T13:41:54.886Z", + "id": "i5Azv4hS7iOZaiHp", + "name": "owntrack" + } + ] +} diff --git a/tracker-db.json b/tracker-db.json deleted file mode 100644 index 2daa82f..0000000 --- a/tracker-db.json +++ /dev/null @@ -1,465 +0,0 @@ -{ - "name": "Telegram Location Tracker - NocoDB", - "nodes": [ - { - "parameters": { - "updates": [ - "message" - ], - "additionalFields": {} - }, - "id": "0ff38437-1b45-4a44-80bb-708f48e3a0d2", - "name": "Telegram Trigger", - "type": "n8n-nodes-base.telegramTrigger", - "typeVersion": 1.1, - "position": [ - 80, - 128 - ], - "webhookId": "telegram-location-webhook", - "credentials": { - "telegramApi": { - "id": "dRHgVQKqowQHIait", - "name": "Telegram account n8n-munich-bot" - } - } - }, - { - "parameters": { - "conditions": { - "string": [ - { - "value1": "={{ $json.message.location }}", - "operation": "isNotEmpty" - } - ] - } - }, - "id": "cf11bd9c-c392-45d0-9fcf-b673c7302f98", - "name": "Hat Location?", - "type": "n8n-nodes-base.if", - "typeVersion": 1, - "position": [ - 304, - 128 - ] - }, - { - "parameters": { - "jsCode": "// Extrahiere Location-Daten für NocoDB\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const location = item.json.message.location;\n const from = item.json.message.from;\n const messageDate = item.json.message.date;\n \n const timestamp = new Date(messageDate * 1000).toISOString();\n const displayTime = new Date(messageDate * 1000).toLocaleString('de-DE');\n \n results.push({\n json: {\n latitude: location.latitude,\n longitude: location.longitude,\n timestamp: timestamp,\n user_id: from.id,\n first_name: from.first_name || '',\n last_name: from.last_name || '',\n username: from.username || '',\n marker_label: `${from.first_name || ''} ${from.last_name || ''}`.trim(),\n display_time: displayTime,\n chat_id: item.json.message.chat.id,\n battery: null,\n speed: null\n }\n });\n}\n\nreturn results;" - }, - "id": "648a42c5-99ce-4c7b-8a85-380cd40050d0", - "name": "Location verarbeiten", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 528, - 112 - ] - }, - { - "parameters": { - "authentication": "nocoDbApiToken", - "operation": "create", - "projectId": "pdxl4cx4dbu9nxi", - "table": "m8pqj5ixgnnrzkg", - "fieldsUi": { - "fieldValues": [ - { - "fieldName": "latitude", - "fieldValue": "={{ $json.latitude }}" - }, - { - "fieldName": "longitude", - "fieldValue": "={{ $json.longitude }}" - }, - { - "fieldName": "timestamp", - "fieldValue": "={{ $json.timestamp }}" - }, - { - "fieldName": "user_id", - "fieldValue": "={{ $json.user_id }}" - }, - { - "fieldName": "first_name", - "fieldValue": "={{ $json.first_name }}" - }, - { - "fieldName": "last_name", - "fieldValue": "={{ $json.last_name }}" - }, - { - "fieldName": "username", - "fieldValue": "={{ $json.username }}" - }, - { - "fieldName": "marker_label", - "fieldValue": "={{ $json.marker_label }}" - }, - { - "fieldName": "display_time", - "fieldValue": "={{ $json.display_time }}" - }, - { - "fieldName": "chat_id", - "fieldValue": "={{ $json.chat_id }}" - }, - { - "fieldName": "battery", - "fieldValue": "={{ $json.battery }}" - }, - { - "fieldName": "speed", - "fieldValue": "={{ $json.speed }}" - } - ] - } - }, - "id": "0bd51abc-74cb-4220-aa42-f7801c8c79f1", - "name": "Speichere in NocoDB", - "type": "n8n-nodes-base.nocoDb", - "typeVersion": 2, - "position": [ - 752, - 112 - ], - "credentials": { - "nocoDbApiToken": { - "id": "6fNBtcghMe8wFoE5", - "name": "NocoDB Token account" - } - } - }, - { - "parameters": { - "authentication": "nocoDbApiToken", - "operation": "getAll", - "projectId": "pdxl4cx4dbu9nxi", - "table": "m8pqj5ixgnnrzkg", - "options": {} - }, - "id": "b87051cc-a53b-4c8e-8380-1e8d1d69e75b", - "name": "Hole letzten Eintrag", - "type": "n8n-nodes-base.nocoDb", - "typeVersion": 2, - "position": [ - 976, - 112 - ], - "credentials": { - "nocoDbApiToken": { - "id": "6fNBtcghMe8wFoE5", - "name": "NocoDB Token account" - } - } - }, - { - "parameters": { - "authentication": "nocoDbApiToken", - "operation": "getAll", - "projectId": "pdxl4cx4dbu9nxi", - "table": "m8pqj5ixgnnrzkg", - "returnAll": true, - "options": {} - }, - "id": "78a7fd6d-640d-4659-b0c7-11fefc827eaf", - "name": "Zähle Einträge", - "type": "n8n-nodes-base.nocoDb", - "typeVersion": 2, - "position": [ - 976, - 240 - ], - "credentials": { - "nocoDbApiToken": { - "id": "6fNBtcghMe8wFoE5", - "name": "NocoDB Token account" - } - } - }, - { - "parameters": { - "jsCode": "// Kombiniere Daten für Telegram-Bestätigung\nconst lastEntry = $('Hole letzten Eintrag').all();\nconst allEntries = $('Zähle Einträge').all();\nconst originalData = $('Location verarbeiten').first().json;\n\nreturn [{\n json: {\n latitude: originalData.latitude,\n longitude: originalData.longitude,\n display_time: originalData.display_time,\n chat_id: originalData.chat_id,\n total: allEntries.length\n }\n}];" - }, - "id": "25429eb3-aa61-428d-9ec2-2f365e9669eb", - "name": "Bereite Bestätigung vor", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1328, - 128 - ] - }, - { - "parameters": { - "chatId": "={{ $json.chat_id }}", - "text": "=✅ Standort gespeichert!\n\n📍 Koordinaten:\nLat: {{ $json.latitude }}\nLon: {{ $json.longitude }}\n\n🕐 Zeit: {{ $json.display_time }}\n\n🗺️ Karte:\nhttps://web.unixweb.home64.de/tracker/index.html\n\n📊 Gespeicherte Punkte: {{ $json.total }}", - "additionalFields": {} - }, - "id": "8a3be707-feb5-42c2-a551-5ebc4aa123f9", - "name": "Telegram Bestätigung", - "type": "n8n-nodes-base.telegram", - "typeVersion": 1.1, - "position": [ - 1520, - 128 - ], - "webhookId": "9df3ad7d-315d-4782-b4ec-4fb154c0b46d", - "credentials": { - "telegramApi": { - "id": "dRHgVQKqowQHIait", - "name": "Telegram account n8n-munich-bot" - } - } - }, - { - "parameters": { - "path": "location", - "responseMode": "lastNode", - "options": { - "allowedOrigins": "*" - } - }, - "id": "e8d5b4b8-0495-4599-be54-a21ff208f26b", - "name": "Webhook - Location API", - "type": "n8n-nodes-base.webhook", - "typeVersion": 1.1, - "position": [ - 80, - 416 - ], - "webhookId": "location-api-endpoint" - }, - { - "parameters": { - "authentication": "nocoDbApiToken", - "operation": "getAll", - "projectId": "pdxl4cx4dbu9nxi", - "table": "m8pqj5ixgnnrzkg", - "returnAll": true, - "options": {} - }, - "id": "61d1ee53-9536-419b-ab5a-aff4281b08ac", - "name": "Lade Daten aus NocoDB", - "type": "n8n-nodes-base.nocoDb", - "typeVersion": 2, - "position": [ - 304, - 416 - ], - "credentials": { - "nocoDbApiToken": { - "id": "6fNBtcghMe8wFoE5", - "name": "NocoDB Token account" - } - } - }, - { - "parameters": { - "jsCode": "// Baue API Response aus NocoDB-Daten\nconst locations = $input.all().map(item => item.json);\n\n// Sortiere nach timestamp (neueste zuerst)\nlocations.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));\n\nconst current = locations.length > 0 ? locations[0] : null;\n\nreturn [{\n json: {\n success: true,\n current: current,\n history: locations,\n total_points: locations.length,\n last_updated: current ? current.timestamp : null\n }\n}];" - }, - "id": "6e2d1c8b-1c23-44d4-9faf-2ab85a200e18", - "name": "Format API Response", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 528, - 416 - ] - }, - { - "parameters": { - "respondWith": "json", - "responseBody": "={{ $json }}", - "options": { - "responseHeaders": { - "entries": [ - { - "name": "Access-Control-Allow-Origin", - "value": "*" - }, - { - "name": "Content-Type", - "value": "application/json" - } - ] - } - } - }, - "id": "81534ffa-a120-437c-a2cd-5b68991d95fe", - "name": "JSON Response", - "type": "n8n-nodes-base.respondToWebhook", - "typeVersion": 1, - "position": [ - 752, - 416 - ] - }, - { - "parameters": { - "height": 544, - "width": 1632, - "color": 4 - }, - "type": "n8n-nodes-base.stickyNote", - "position": [ - 0, - 0 - ], - "typeVersion": 1, - "id": "4fe64055-f0c3-4377-a4f5-be668e6856ef", - "name": "Sticky Note" - }, - { - "parameters": {}, - "type": "n8n-nodes-base.merge", - "typeVersion": 3.2, - "position": [ - 1168, - 128 - ], - "id": "c7c605e5-9033-4542-a5b5-7847080de63b", - "name": "Merge" - } - ], - "pinData": {}, - "connections": { - "Telegram Trigger": { - "main": [ - [ - { - "node": "Hat Location?", - "type": "main", - "index": 0 - } - ] - ] - }, - "Hat Location?": { - "main": [ - [ - { - "node": "Location verarbeiten", - "type": "main", - "index": 0 - } - ] - ] - }, - "Location verarbeiten": { - "main": [ - [ - { - "node": "Speichere in NocoDB", - "type": "main", - "index": 0 - } - ] - ] - }, - "Speichere in NocoDB": { - "main": [ - [ - { - "node": "Hole letzten Eintrag", - "type": "main", - "index": 0 - }, - { - "node": "Zähle Einträge", - "type": "main", - "index": 0 - } - ] - ] - }, - "Hole letzten Eintrag": { - "main": [ - [ - { - "node": "Merge", - "type": "main", - "index": 0 - } - ] - ] - }, - "Zähle Einträge": { - "main": [ - [ - { - "node": "Merge", - "type": "main", - "index": 1 - } - ] - ] - }, - "Bereite Bestätigung vor": { - "main": [ - [ - { - "node": "Telegram Bestätigung", - "type": "main", - "index": 0 - } - ] - ] - }, - "Webhook - Location API": { - "main": [ - [ - { - "node": "Lade Daten aus NocoDB", - "type": "main", - "index": 0 - } - ] - ] - }, - "Lade Daten aus NocoDB": { - "main": [ - [ - { - "node": "Format API Response", - "type": "main", - "index": 0 - } - ] - ] - }, - "Format API Response": { - "main": [ - [ - { - "node": "JSON Response", - "type": "main", - "index": 0 - } - ] - ] - }, - "Merge": { - "main": [ - [ - { - "node": "Bereite Bestätigung vor", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "active": true, - "settings": { - "executionOrder": "v1" - }, - "versionId": "2fe5806f-3c70-4680-b7eb-66f72a413ea9", - "meta": { - "instanceId": "12d864c68e4fb5dfd100dc0c683b95f43cd55af7e9efa82e25407fac5a3824a5" - }, - "id": "8fjExqEGYvh8XWVu", - "tags": [] -} \ No newline at end of file diff --git a/tracker-mqtt.json b/tracker-mqtt.json deleted file mode 100644 index b2ff97f..0000000 --- a/tracker-mqtt.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "name": "MQTT Location Tracker - NocoDB", - "nodes": [ - { - "parameters": { - "topics": "owntracks/#", - "options": {} - }, - "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", - "name": "MQTT Trigger", - "type": "n8n-nodes-base.mqttTrigger", - "typeVersion": 1, - "position": [ - 80, - 128 - ], - "credentials": { - "mqtt": { - "id": "MQTT_CREDENTIAL_ID", - "name": "MQTT Broker" - } - } - }, - { - "parameters": { - "conditions": { - "string": [ - { - "value1": "={{ $json._type }}", - "value2": "location" - } - ] - } - }, - "id": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", - "name": "Ist Location?", - "type": "n8n-nodes-base.if", - "typeVersion": 1, - "position": [ - 304, - 128 - ] - }, - { - "parameters": { - "jsCode": "// Extrahiere MQTT Location-Daten für NocoDB\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const data = item.json;\n \n // Validiere erforderliche Felder\n if (!data.lat || !data.lon || !data.tst) {\n continue; // Überspringe ungültige Einträge\n }\n \n // Timestamp (tst = Unix timestamp in seconds)\n // Verwende tst oder fallback auf aktuelle Zeit\n const timestampMs = (data.tst && !isNaN(data.tst)) ? data.tst * 1000 : Date.now();\n const timestamp = new Date(timestampMs).toISOString();\n const displayTime = new Date(timestampMs).toLocaleString('de-DE');\n \n // Erstelle marker_label aus tid (tracker ID)\n const trackerLabel = data.tid || 'Unknown';\n \n // Optional: Verwende SSID als zusätzliche Info wenn vorhanden\n const markerLabel = data.SSID ? `${trackerLabel} (${data.SSID})` : trackerLabel;\n \n results.push({\n json: {\n latitude: data.lat,\n longitude: data.lon,\n timestamp: timestamp,\n user_id: 0, // MQTT hat keine User-ID, default auf 0\n first_name: trackerLabel,\n last_name: data.source || 'mqtt', // z.B. \"fused\"\n username: data.tid || '',\n marker_label: markerLabel,\n display_time: displayTime,\n chat_id: 0, // MQTT hat keine Chat-ID, default auf 0\n // Optional: Zusätzliche MQTT-Daten als Referenz\n mqtt_data: {\n accuracy: data.acc,\n altitude: data.alt,\n battery: data.batt,\n velocity: data.vel,\n course: data.cog,\n connection: data.conn,\n bssid: data.BSSID\n }\n }\n });\n}\n\nreturn results;" - }, - "id": "c3d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f", - "name": "MQTT Location verarbeiten", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 528, - 112 - ] - }, - { - "parameters": { - "authentication": "nocoDbApiToken", - "operation": "create", - "projectId": "pdxl4cx4dbu9nxi", - "table": "m8pqj5ixgnnrzkg", - "fieldsUi": { - "fieldValues": [ - { - "fieldName": "latitude", - "fieldValue": "={{ $json.latitude }}" - }, - { - "fieldName": "longitude", - "fieldValue": "={{ $json.longitude }}" - }, - { - "fieldName": "timestamp", - "fieldValue": "={{ $json.timestamp }}" - }, - { - "fieldName": "user_id", - "fieldValue": "={{ $json.user_id }}" - }, - { - "fieldName": "first_name", - "fieldValue": "={{ $json.first_name }}" - }, - { - "fieldName": "last_name", - "fieldValue": "={{ $json.last_name }}" - }, - { - "fieldName": "username", - "fieldValue": "={{ $json.username }}" - }, - { - "fieldName": "marker_label", - "fieldValue": "={{ $json.marker_label }}" - }, - { - "fieldName": "display_time", - "fieldValue": "={{ $json.display_time }}" - }, - { - "fieldName": "chat_id", - "fieldValue": "={{ $json.chat_id }}" - } - ] - } - }, - "id": "d4e5f6a7-8b9c-0d1e-2f3a-4b5c6d7e8f9a", - "name": "Speichere in NocoDB", - "type": "n8n-nodes-base.nocoDb", - "typeVersion": 2, - "position": [ - 752, - 112 - ], - "credentials": { - "nocoDbApiToken": { - "id": "6fNBtcghMe8wFoE5", - "name": "NocoDB Token account" - } - } - }, - { - "parameters": { - "height": 320, - "width": 896, - "color": 5 - }, - "type": "n8n-nodes-base.stickyNote", - "position": [ - 0, - 0 - ], - "typeVersion": 1, - "id": "e5f6a7b8-9c0d-1e2f-3a4b-5c6d7e8f9a0b", - "name": "Sticky Note" - } - ], - "pinData": {}, - "connections": { - "MQTT Trigger": { - "main": [ - [ - { - "node": "Ist Location?", - "type": "main", - "index": 0 - } - ] - ] - }, - "Ist Location?": { - "main": [ - [ - { - "node": "MQTT Location verarbeiten", - "type": "main", - "index": 0 - } - ] - ] - }, - "MQTT Location verarbeiten": { - "main": [ - [ - { - "node": "Speichere in NocoDB", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "active": false, - "settings": { - "executionOrder": "v1" - }, - "versionId": "mqtt-location-tracker-v1", - "meta": { - "instanceId": "12d864c68e4fb5dfd100dc0c683b95f43cd55af7e9efa82e25407fac5a3824a5" - }, - "id": "MqttLocationTracker01", - "tags": [] -} diff --git a/tracker.json b/tracker.json deleted file mode 100644 index ad05e15..0000000 --- a/tracker.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "name": "Telegram Location Tracker - Native (No DB)", - "nodes": [ - { - "parameters": { - "updates": [ - "message" - ], - "additionalFields": {} - }, - "id": "b7769320-830c-4a76-9086-ea4067969ad7", - "name": "Telegram Trigger", - "type": "n8n-nodes-base.telegramTrigger", - "typeVersion": 1.1, - "position": [ - 0, - 16 - ], - "webhookId": "telegram-location-webhook", - "credentials": { - "telegramApi": { - "id": "dRHgVQKqowQHIait", - "name": "Telegram account n8n-munich-bot" - } - } - }, - { - "parameters": { - "conditions": { - "string": [ - { - "value1": "={{ $json.message.location }}", - "operation": "isNotEmpty" - } - ] - } - }, - "id": "57f03b50-3184-4d1e-9d33-f628f93f466b", - "name": "Hat Location?", - "type": "n8n-nodes-base.if", - "typeVersion": 1, - "position": [ - 224, - 16 - ] - }, - { - "parameters": { - "jsCode": "// Extrahiere Location-Daten\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const location = item.json.message.location;\n const from = item.json.message.from;\n const messageDate = item.json.message.date;\n \n const timestamp = new Date(messageDate * 1000).toISOString();\n const displayTime = new Date(messageDate * 1000).toLocaleString('de-DE');\n \n results.push({\n json: {\n latitude: location.latitude,\n longitude: location.longitude,\n timestamp: timestamp,\n user_id: from.id,\n first_name: from.first_name || '',\n last_name: from.last_name || '',\n username: from.username || '',\n marker_label: `${from.first_name || ''} ${from.last_name || ''}`.trim(),\n display_time: displayTime,\n chat_id: item.json.message.chat.id\n }\n });\n}\n\nreturn results;" - }, - "id": "dafcade6-0563-47b9-bd0d-dff0f61d1dbb", - "name": "Location verarbeiten", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 448, - 0 - ] - }, - { - "parameters": { - "command": "cat /tmp/n8n-locations.json 2>/dev/null || echo '[]'" - }, - "id": "d3b9945b-4d17-40c0-ae30-3d45801bae1e", - "name": "Lade existierende Daten", - "type": "n8n-nodes-base.executeCommand", - "typeVersion": 1, - "position": [ - 672, - 0 - ] - }, - { - "parameters": { - "jsCode": "// Neue Location zu File hinzufügen\nconst newLocation = $('Location verarbeiten').first().json;\n\n// Lade bestehende Locations\nlet locations = [];\ntry {\n const fileContent = $input.first().json.stdout;\n locations = JSON.parse(fileContent);\n} catch (e) {\n locations = [];\n}\n\n// Füge neue Location hinzu\nlocations.unshift(newLocation); // Am Anfang einfügen (neueste zuerst)\n\n// Behalte nur die letzten 100 Einträge\nif (locations.length > 100) {\n locations = locations.slice(0, 100);\n}\n\nreturn [{\n json: {\n locations: locations,\n json_string: JSON.stringify(locations, null, 2),\n current: newLocation,\n total: locations.length\n }\n}];" - }, - "id": "4fadae07-460f-4c29-800b-1fdbffe25991", - "name": "Merge mit History", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 880, - 0 - ] - }, - { - "parameters": { - "command": "=echo '{{ $json.json_string.replace(/'/g, \"'\\\\'''\") }}' > /tmp/n8n-locations.json" - }, - "id": "c519118b-0626-4ace-befc-ee9859d73504", - "name": "Speichere in File", - "type": "n8n-nodes-base.executeCommand", - "typeVersion": 1, - "position": [ - 1104, - 0 - ] - }, - { - "parameters": { - "chatId": "={{ $('Location verarbeiten').first().json.chat_id }}", - "text": "=✅ Standort gespeichert!\n\n📍 Koordinaten:\nLat: {{ $('Location verarbeiten').first().json.latitude }}\nLon: {{ $('Location verarbeiten').first().json.longitude }}\n\n🕐 Zeit: {{ $('Location verarbeiten').first().json.display_time }}\n\n🗺️ Karte:\nhttps://web.unixweb.home64.de/tracker/index.html\n\n📊 Gespeicherte Punkte: {{ $json.total }}", - "additionalFields": {} - }, - "id": "aa023c84-7664-4641-b3fc-cd30b9f69941", - "name": "Telegram Bestätigung", - "type": "n8n-nodes-base.telegram", - "typeVersion": 1.1, - "position": [ - 1328, - 0 - ], - "webhookId": "9df3ad7d-315d-4782-b4ec-4fb154c0b46d", - "credentials": { - "telegramApi": { - "id": "dRHgVQKqowQHIait", - "name": "Telegram account n8n-munich-bot" - } - } - }, - { - "parameters": { - "path": "location", - "responseMode": "lastNode", - "options": { - "allowedOrigins": "*" - } - }, - "id": "b47e8370-3276-4f49-a2c5-25ef07b83ce8", - "name": "Webhook - Location API", - "type": "n8n-nodes-base.webhook", - "typeVersion": 1.1, - "position": [ - 0, - 208 - ], - "webhookId": "location-api-endpoint" - }, - { - "parameters": { - "command": "cat /tmp/n8n-locations.json 2>/dev/null || echo '[]'" - }, - "id": "200dbfff-2932-4e92-9e47-181ff706c0de", - "name": "Lade Daten für API", - "type": "n8n-nodes-base.executeCommand", - "typeVersion": 1, - "position": [ - 224, - 208 - ] - }, - { - "parameters": { - "jsCode": "// Parse locations und baue API Response\nlet locations = [];\ntry {\n const fileContent = $input.first().json.stdout;\n locations = JSON.parse(fileContent);\n} catch (e) {\n locations = [];\n}\n\nconst current = locations.length > 0 ? locations[0] : null;\n\nreturn [{\n json: {\n success: true,\n current: current,\n history: locations,\n total_points: locations.length,\n last_updated: current ? current.timestamp : null\n }\n}];" - }, - "id": "7c56b850-f1dd-4c51-ac8d-4313e199e6e7", - "name": "Format API Response", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 448, - 208 - ] - }, - { - "parameters": { - "respondWith": "json", - "responseBody": "={{ $json }}", - "options": { - "responseHeaders": { - "entries": [ - { - "name": "Access-Control-Allow-Origin", - "value": "*" - }, - { - "name": "Content-Type", - "value": "application/json" - } - ] - } - } - }, - "id": "9b720b0b-b4f8-4599-9e2f-d6e8f67819ab", - "name": "JSON Response", - "type": "n8n-nodes-base.respondToWebhook", - "typeVersion": 1, - "position": [ - 672, - 208 - ] - }, - { - "parameters": { - "height": 544, - "width": 1632, - "color": 4 - }, - "type": "n8n-nodes-base.stickyNote", - "position": [ - -80, - -112 - ], - "typeVersion": 1, - "id": "29506fa4-6cb3-403f-bd64-6a2f49a2eb85", - "name": "Sticky Note" - } - ], - "pinData": {}, - "connections": { - "Telegram Trigger": { - "main": [ - [ - { - "node": "Hat Location?", - "type": "main", - "index": 0 - } - ] - ] - }, - "Hat Location?": { - "main": [ - [ - { - "node": "Location verarbeiten", - "type": "main", - "index": 0 - } - ] - ] - }, - "Location verarbeiten": { - "main": [ - [ - { - "node": "Lade existierende Daten", - "type": "main", - "index": 0 - } - ] - ] - }, - "Lade existierende Daten": { - "main": [ - [ - { - "node": "Merge mit History", - "type": "main", - "index": 0 - } - ] - ] - }, - "Merge mit History": { - "main": [ - [ - { - "node": "Speichere in File", - "type": "main", - "index": 0 - } - ] - ] - }, - "Speichere in File": { - "main": [ - [ - { - "node": "Telegram Bestätigung", - "type": "main", - "index": 0 - } - ] - ] - }, - "Webhook - Location API": { - "main": [ - [ - { - "node": "Lade Daten für API", - "type": "main", - "index": 0 - } - ] - ] - }, - "Lade Daten für API": { - "main": [ - [ - { - "node": "Format API Response", - "type": "main", - "index": 0 - } - ] - ] - }, - "Format API Response": { - "main": [ - [ - { - "node": "JSON Response", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "active": true, - "settings": { - "executionOrder": "v1", - "callerPolicy": "workflowsFromSameOwner", - "availableInMCP": false, - "errorWorkflow": "PhwIkaqyXRasTXDH" - }, - "versionId": "b87a412a-4833-4f0d-aa33-0ba9074238d9", - "meta": { - "instanceId": "12d864c68e4fb5dfd100dc0c683b95f43cd55af7e9efa82e25407fac5a3824a5" - }, - "id": "5dUTiJE61r7opoul", - "tags": [] -}