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": []
-}