Überarbeite Dokumentation auf aktuellen MQTT-only Stand
- README.md komplett neu geschrieben: - Fokus auf n8n-tracker.json (MQTT-only) - Entfernt: Telegram-Workflows, Datei-basierte Speicherung - Hinzugefügt: OwnTracks-Setup, Geräte-Mapping, erweiterte Features - Neue Sektionen: Sicherheitshinweise, DSGVO-Compliance - Praktische Code-Beispiele für Customization - CLAUDE.md aktualisiert: - Neue Workflow-Architektur dokumentiert - NocoDB-Schema mit battery/speed Feldern - Web-Interface Details (Filter, Kartenebenen, Marker) - Wichtige Gotchas und Edge Cases hinzugefügt - Dateien bereinigt: - Gelöscht: tracker.json, tracker-db.json, tracker-mqtt.json - Gelöscht: index_owntrack.html, locations-example.csv - Hinzugefügt: n8n-tracker.json (aktueller Workflow) - Hinzugefügt: database-example.csv (aktuelles Schema) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
474
CLAUDE.md
474
CLAUDE.md
@@ -4,108 +4,109 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Overview
|
## 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)
|
- **n8n-tracker.json** - MQTT/OwnTracks workflow with NocoDB storage
|
||||||
2. **tracker-db.json** - Telegram-based with NocoDB storage (production-ready, persistent storage)
|
- **index.html** - Web interface with device filtering, time-based filtering, and multiple map layers
|
||||||
3. **tracker-mqtt.json** - MQTT-based with NocoDB storage (for OwnTracks/MQTT location data)
|
|
||||||
|
|
||||||
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
|
## 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):
|
**Key nodes:**
|
||||||
- Telegram Trigger → Hat Location? → Location verarbeiten → Lade existierende Daten → Merge mit History → Speichere in File → Telegram Bestätigung
|
- **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):
|
### 2. Location API Flow
|
||||||
- Telegram Trigger → Hat Location? → Location verarbeiten → Speichere in NocoDB → [Hole letzten Eintrag + Zähle Einträge] → Merge → Bereite Bestätigung vor → Telegram Bestätigung
|
|
||||||
|
|
||||||
Key nodes:
|
```
|
||||||
- **Telegram Trigger**: Receives incoming Telegram messages
|
Webhook - Location API (GET /webhook/location)
|
||||||
- **Hat Location?**: Filters messages containing location data
|
↓
|
||||||
- **Location verarbeiten**: Extracts and formats location data (lat/lon, user info, timestamp) using JavaScript
|
Lade Daten aus NocoDB (Get all records)
|
||||||
- **Storage**: Either file-based (shell commands) or NocoDB API calls
|
↓
|
||||||
- **Telegram Bestätigung**: Sends confirmation with location details and map link
|
Format API Response (JavaScript: Sort, structure JSON)
|
||||||
|
↓
|
||||||
|
JSON Response (CORS-enabled)
|
||||||
|
```
|
||||||
|
|
||||||
**2. Location API Flow (Webhook → JSON Response)** - See section below
|
**Key nodes:**
|
||||||
|
- **Webhook - Location API**: Public endpoint at `/webhook/location` with CORS enabled
|
||||||
### MQTT-based Workflow
|
- **Lade Daten aus NocoDB**: Fetches all location records from database
|
||||||
|
- **Format API Response**: Sorts by timestamp (newest first), builds response structure
|
||||||
**tracker-mqtt.json** has a simpler, single-path architecture:
|
- **JSON Response**: Returns structured JSON with CORS headers
|
||||||
|
|
||||||
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 Technical Details
|
## Key Technical Details
|
||||||
|
|
||||||
### Data Storage
|
### Data Storage
|
||||||
|
|
||||||
**tracker.json (File-based)**:
|
**NocoDB Database Configuration:**
|
||||||
- Location: `/tmp/n8n-locations.json`
|
- **Project ID**: `pdxl4cx4dbu9nxi`
|
||||||
- Format: JSON array of location objects
|
- **Table ID**: `m8pqj5ixgnnrzkg`
|
||||||
- Max retention: 100 most recent locations (oldest automatically removed)
|
- **Credential ID**: `T9XuGr6CJD2W2BPO` (NocoDB Token account)
|
||||||
- Persistence: Survives n8n restarts but may be lost on system restart due to `/tmp` location
|
- **Persistence**: Full database persistence (no client-side limit)
|
||||||
|
|
||||||
**tracker-db.json & tracker-mqtt.json (NocoDB)**:
|
### NocoDB Schema
|
||||||
- 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
|
|
||||||
|
|
||||||
### 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:
|
```
|
||||||
|
latitude (Decimal) - Geographic latitude
|
||||||
```json
|
longitude (Decimal) - Geographic longitude
|
||||||
{
|
timestamp (DateTime) - ISO 8601 timestamp
|
||||||
"latitude": number,
|
user_id (Number) - Always 0 for MQTT devices
|
||||||
"longitude": number,
|
first_name (Text) - Tracker ID (e.g., "10", "11")
|
||||||
"timestamp": "ISO 8601 string",
|
last_name (Text) - Source type (e.g., "fused")
|
||||||
"user_id": number,
|
username (Text) - Same as tracker ID
|
||||||
"first_name": string,
|
marker_label (Text) - Display label for map markers
|
||||||
"last_name": string,
|
display_time (Text) - Formatted timestamp (de-DE locale)
|
||||||
"username": string,
|
chat_id (Number) - Always 0 for MQTT devices
|
||||||
"marker_label": string,
|
battery (Number) - Battery percentage (0-100)
|
||||||
"display_time": "de-DE locale string",
|
speed (Decimal) - Velocity in m/s
|
||||||
"chat_id": number
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Data Mapping for MQTT/OwnTracks**:
|
### OwnTracks Data Mapping
|
||||||
- `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)
|
|
||||||
|
|
||||||
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
|
### API Response Structure
|
||||||
```json
|
```json
|
||||||
@@ -118,116 +119,257 @@ Additional MQTT data (accuracy, altitude, battery, velocity, etc.) is available
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Web Interface
|
### Web Interface (index.html)
|
||||||
The workflow sends users a link to `https://web.unixweb.home64.de/tracker/index.html` for viewing locations on a map.
|
|
||||||
|
|
||||||
The `index.html` file in this repository provides a standalone web interface:
|
The web interface is a single-page application built with Leaflet.js:
|
||||||
- **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
|
|
||||||
|
|
||||||
## 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)
|
**Key Features:**
|
||||||
- **Telegram Credentials**: "Telegram account n8n-munich-bot" (ID: `dRHgVQKqowQHIait`)
|
1. **Multiple Map Layers** (lines 158-171):
|
||||||
- **Webhook IDs**:
|
- Standard (OpenStreetMap)
|
||||||
- Telegram trigger: `telegram-location-webhook`
|
- Satellite (Esri World Imagery)
|
||||||
- Location API: `location-api-endpoint`
|
- 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
|
- **Execution Order**: v1
|
||||||
- **Caller Policy**: workflowsFromSameOwner
|
- **Caller Policy**: workflowsFromSameOwner
|
||||||
- **Status**: Both workflows set to `active: true` by default
|
- **Status**: `active: true`
|
||||||
|
- **Error Workflow**: `0bBZzSE6SUzVsif5`
|
||||||
|
- **Tags**: "owntrack"
|
||||||
|
|
||||||
### tracker.json Specific
|
**Credentials:**
|
||||||
- **Storage**: Shell commands (cat, echo) for file I/O
|
- **MQTT**: Credential ID `L07VVR2BDfDda6Zo` ("MQTT account")
|
||||||
- **Error Workflow**: ID `PhwIkaqyXRasTXDH` (configured but not in this export)
|
- **NocoDB**: Credential ID `T9XuGr6CJD2W2BPO` ("NocoDB Token account")
|
||||||
|
|
||||||
### tracker-db.json Specific
|
**Node Configuration:**
|
||||||
- **NocoDB Credentials**: "NocoDB Token account" (ID: `6fNBtcghMe8wFoE5`)
|
- **MQTT Trigger**:
|
||||||
- **Project ID**: `pdxl4cx4dbu9nxi`
|
- Topic: `owntracks/#`
|
||||||
- **Table ID**: `m8pqj5ixgnnrzkg`
|
- Subscribes to all OwnTracks topics
|
||||||
- **Sort Order**: Uses array structure `[{field: "timestamp", direction: "desc"}]` for sorting locations
|
- No message filtering at trigger level (all messages pass through)
|
||||||
|
|
||||||
### tracker-mqtt.json Specific
|
- **MQTT Location verarbeiten** (Code Node):
|
||||||
- **MQTT Credentials**: Requires MQTT broker credentials (placeholder ID: `MQTT_CREDENTIAL_ID`)
|
- Parses JSON from `message` field
|
||||||
- **Topic**: `owntracks/#` (subscribes to all OwnTracks topics)
|
- Validates required fields: `lat`, `lon`, `tst`
|
||||||
- **Message Filter**: Only processes messages with `_type: "location"`
|
- Skips invalid messages with `continue`
|
||||||
- **NocoDB Config**: Same as tracker-db.json (shares database)
|
- Sets `alwaysOutputData: true` to handle empty results
|
||||||
- **Status**: Set to `active: false` by default (activate after configuring MQTT credentials)
|
- 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
|
- **Webhook - Location API**:
|
||||||
1. **Both workflows are active by default** - test changes carefully to avoid disrupting live tracking
|
- Path: `/location`
|
||||||
2. **JavaScript code nodes** use n8n's execution environment (not vanilla Node.js)
|
- Webhook ID: `location-api-endpoint`
|
||||||
3. **Date formatting** uses `de-DE` locale - change in "Location verarbeiten" node if needed
|
- Response Mode: `lastNode`
|
||||||
4. **CORS** is configured for `*` (all origins) - restrict for production security
|
- CORS: Allowed origins = `*`
|
||||||
|
|
||||||
### tracker.json Specific
|
## Common Modifications
|
||||||
- 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)
|
|
||||||
|
|
||||||
### tracker-db.json & tracker-mqtt.json Specific
|
### Adding a New Device
|
||||||
- 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
|
|
||||||
|
|
||||||
### tracker-mqtt.json Specific
|
**Step 1: Update index.html device mappings (lines 142-152)**
|
||||||
- Requires MQTT broker configuration before activation
|
```javascript
|
||||||
- Topic pattern can be adjusted to match your MQTT setup
|
const DEVICE_NAMES = {
|
||||||
- Data transformation maps OwnTracks format to NocoDB schema
|
'10': 'Joachim Pixel',
|
||||||
- MQTT data fields (accuracy, battery, velocity) are extracted but not persisted to database
|
'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)**:
|
**Step 2: Configure OwnTracks app**
|
||||||
In "Merge mit History" node, change `locations.slice(0, 100)` to desired limit
|
- 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**:
|
### Changing Date/Time Format
|
||||||
In "Location verarbeiten" node, change `.toLocaleString('de-DE')` to desired locale
|
|
||||||
|
|
||||||
**Restrict CORS**:
|
**In n8n workflow node "MQTT Location verarbeiten" (line 124):**
|
||||||
In "Webhook - Location API" node, change `Access-Control-Allow-Origin: *` to specific domain
|
```javascript
|
||||||
|
// Current: German format with Berlin timezone
|
||||||
|
const displayTime = new Date(timestampMs).toLocaleString('de-DE', { timeZone: 'Europe/Berlin' });
|
||||||
|
|
||||||
**Update web interface URL**:
|
// Change to US format:
|
||||||
In "Telegram Bestätigung" node and `index.html:85`, update API endpoint URL
|
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
|
## Repository Contents
|
||||||
|
|
||||||
- **tracker.json** - Telegram with file-based storage (simple, no database)
|
| File | Description |
|
||||||
- **tracker-db.json** - Telegram with NocoDB storage (production-ready)
|
|------|-------------|
|
||||||
- **tracker-mqtt.json** - MQTT/OwnTracks with NocoDB storage (IoT devices)
|
| `n8n-tracker.json` | n8n workflow - MQTT location capture + API endpoint |
|
||||||
- **index.html** - Leaflet.js web interface for map visualization
|
| `index.html` | Web interface with multi-layer maps and device filtering |
|
||||||
- **locations-example.csv** - Example data format for testing
|
| `database-example.csv` | Sample NocoDB export showing actual data structure |
|
||||||
- **README.md** - Detailed German documentation with setup and usage instructions
|
| `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
|
### 2. MQTT Message Validation
|
||||||
1. Configure MQTT broker credentials in n8n
|
- The workflow does NOT filter by `_type: "location"` despite what older documentation says
|
||||||
2. Update the credential ID in tracker-mqtt.json (currently placeholder: `MQTT_CREDENTIAL_ID`)
|
- All MQTT messages are processed; validation happens in the JavaScript code node
|
||||||
3. Import workflow into n8n
|
- Messages missing `lat`, `lon`, or `tst` are silently skipped with `continue`
|
||||||
4. Activate the workflow
|
- **Result**: Non-location MQTT messages don't cause errors, they're just ignored
|
||||||
|
|
||||||
### OwnTracks Configuration
|
### 3. Time Filter Default
|
||||||
Configure your OwnTracks app/device to publish to the same MQTT broker:
|
- The web interface defaults to **1 hour** time filter (line 125)
|
||||||
- **Topic pattern**: `owntracks/user/device` (workflow subscribes to `owntracks/#`)
|
- This means newly deployed users won't see historical data unless they change the filter
|
||||||
- **Mode**: MQTT
|
- Consider changing default to `24h` or `all` for better initial experience
|
||||||
- **Expected message format**: JSON with `_type: "location"`
|
|
||||||
|
|
||||||
### Data Flow
|
### 4. Circular Marker Icon Implementation
|
||||||
1. OwnTracks device publishes location to MQTT broker
|
- Markers use SVG `divIcon` with a navigation-style clock hand (lines 337-345)
|
||||||
2. n8n MQTT trigger receives message
|
- The clock hand is purely decorative, does NOT represent actual direction/heading
|
||||||
3. Filter checks for `_type: "location"`
|
- This replaced standard Leaflet pin icons in recent commits (see commit 4bec87d)
|
||||||
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
|
|
||||||
|
|
||||||
### Distinguishing Data Sources
|
### 5. Speed Unit Conversion
|
||||||
In the database:
|
- OwnTracks sends velocity in **m/s** (`vel` field)
|
||||||
- **Telegram entries**: Have real `user_id` and `chat_id` values, `first_name`/`last_name` from Telegram profile
|
- Stored in database as m/s in `speed` column
|
||||||
- **MQTT entries**: Have `user_id: 0` and `chat_id: 0`, use `tid` (tracker ID) in name fields
|
- 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
|
||||||
|
|||||||
12
database-example.csv
Normal file
12
database-example.csv
Normal file
@@ -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
|
||||||
|
|
||||||
|
@@ -1,224 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Location Test</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
||||||
<style>
|
|
||||||
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
|
|
||||||
#map { height: 100vh; width: 100%; }
|
|
||||||
.info {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
background: white;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
.toggle-btn {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
width: 100%;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.toggle-btn.active {
|
|
||||||
background: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.toggle-btn.inactive {
|
|
||||||
background: #f44336;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.toggle-btn:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
.status-indicator {
|
|
||||||
display: inline-block;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
.status-indicator.active {
|
|
||||||
background: #4CAF50;
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
.status-indicator.inactive {
|
|
||||||
background: #999;
|
|
||||||
}
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="map"></div>
|
|
||||||
<div class="info">
|
|
||||||
<h3>📍 Location Tracker</h3>
|
|
||||||
<div id="status">Lade...</div>
|
|
||||||
<button id="toggleBtn" class="toggle-btn active" onclick="toggleAutoRefresh()">
|
|
||||||
<span class="status-indicator active"></span>
|
|
||||||
Auto-Refresh: AN
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
||||||
<script>
|
|
||||||
// Karte initialisieren (München)
|
|
||||||
const map = L.map('map').setView([48.1351, 11.5820], 12);
|
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
attribution: '© OpenStreetMap'
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
// API URL - anpassen an deine Domain
|
|
||||||
const API_URL = 'https://n8n.unixweb.home64.de/webhook/location';
|
|
||||||
|
|
||||||
// Auto-Refresh State
|
|
||||||
let autoRefreshEnabled = true;
|
|
||||||
let refreshInterval = null;
|
|
||||||
let markers = [];
|
|
||||||
let polylines = [];
|
|
||||||
|
|
||||||
async function loadLocations() {
|
|
||||||
try {
|
|
||||||
console.log('Fetching locations from:', API_URL);
|
|
||||||
const response = await fetch(API_URL);
|
|
||||||
console.log('Response status:', response.status);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('Data received:', data);
|
|
||||||
|
|
||||||
// Alte Marker und Linien entfernen
|
|
||||||
markers.forEach(marker => map.removeLayer(marker));
|
|
||||||
polylines.forEach(polyline => map.removeLayer(polyline));
|
|
||||||
markers = [];
|
|
||||||
polylines = [];
|
|
||||||
|
|
||||||
// Status aktualisieren
|
|
||||||
const statusDiv = document.getElementById('status');
|
|
||||||
statusDiv.innerHTML =
|
|
||||||
`Punkte: ${data.total_points || 0}<br>` +
|
|
||||||
`Status: ✅ Verbunden`;
|
|
||||||
|
|
||||||
if (data.current) {
|
|
||||||
const loc = data.current;
|
|
||||||
console.log('Current location object:', loc);
|
|
||||||
console.log('latitude field:', loc.latitude, 'type:', typeof loc.latitude);
|
|
||||||
console.log('longitude field:', loc.longitude, 'type:', typeof loc.longitude);
|
|
||||||
|
|
||||||
const lat = parseFloat(loc.latitude);
|
|
||||||
const lon = parseFloat(loc.longitude);
|
|
||||||
|
|
||||||
console.log('Parsed lat:', lat, 'lon:', lon);
|
|
||||||
|
|
||||||
if (isNaN(lat) || isNaN(lon)) {
|
|
||||||
throw new Error(`Invalid coordinates: lat=${loc.latitude} (${typeof loc.latitude}), lon=${loc.longitude} (${typeof loc.longitude})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popup-Inhalt aufbauen
|
|
||||||
let popupContent = `${loc.marker_label || 'Unknown'}<br>${loc.display_time || ''}`;
|
|
||||||
|
|
||||||
// Batterie hinzufügen, falls vorhanden
|
|
||||||
if (loc.battery !== undefined && loc.battery !== null) {
|
|
||||||
popupContent += `<br>🔋 Batterie: ${loc.battery}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Geschwindigkeit hinzufügen, falls vorhanden
|
|
||||||
if (loc.speed !== undefined && loc.speed !== null) {
|
|
||||||
const speedKmh = (loc.speed * 3.6).toFixed(1); // m/s zu km/h
|
|
||||||
popupContent += `<br>🚗 Speed: ${speedKmh} km/h`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const marker = L.marker([lat, lon])
|
|
||||||
.addTo(map)
|
|
||||||
.bindPopup(popupContent)
|
|
||||||
.openPopup();
|
|
||||||
|
|
||||||
markers.push(marker);
|
|
||||||
|
|
||||||
map.setView([lat, lon], 15);
|
|
||||||
|
|
||||||
// Historie als Linie
|
|
||||||
if (data.history && data.history.length > 1) {
|
|
||||||
const coords = data.history
|
|
||||||
.map(h => {
|
|
||||||
const hlat = parseFloat(h.latitude);
|
|
||||||
const hlon = parseFloat(h.longitude);
|
|
||||||
if (!isNaN(hlat) && !isNaN(hlon)) {
|
|
||||||
return [hlat, hlon];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(c => c !== null);
|
|
||||||
|
|
||||||
if (coords.length > 1) {
|
|
||||||
const polyline = L.polyline(coords, {color: 'blue', weight: 3}).addTo(map);
|
|
||||||
polylines.push(polyline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No current location in response');
|
|
||||||
statusDiv.innerHTML += '<br>Keine aktuelle Position';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Load error:', error);
|
|
||||||
console.error('Error stack:', error.stack);
|
|
||||||
document.getElementById('status').innerHTML =
|
|
||||||
`❌ Verbindungsfehler<br><small>${error.message}</small>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAutoRefresh() {
|
|
||||||
autoRefreshEnabled = !autoRefreshEnabled;
|
|
||||||
const btn = document.getElementById('toggleBtn');
|
|
||||||
const indicator = btn.querySelector('.status-indicator');
|
|
||||||
|
|
||||||
if (autoRefreshEnabled) {
|
|
||||||
// Aktiviere Auto-Refresh
|
|
||||||
btn.className = 'toggle-btn active';
|
|
||||||
btn.innerHTML = '<span class="status-indicator active"></span>Auto-Refresh: AN';
|
|
||||||
startAutoRefresh();
|
|
||||||
} else {
|
|
||||||
// Deaktiviere Auto-Refresh
|
|
||||||
btn.className = 'toggle-btn inactive';
|
|
||||||
btn.innerHTML = '<span class="status-indicator inactive"></span>Auto-Refresh: AUS';
|
|
||||||
stopAutoRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startAutoRefresh() {
|
|
||||||
if (refreshInterval) clearInterval(refreshInterval);
|
|
||||||
refreshInterval = setInterval(loadLocations, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopAutoRefresh() {
|
|
||||||
if (refreshInterval) {
|
|
||||||
clearInterval(refreshInterval);
|
|
||||||
refreshInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial laden
|
|
||||||
loadLocations();
|
|
||||||
|
|
||||||
// Auto-refresh starten
|
|
||||||
startAutoRefresh();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
290
n8n-tracker.json
Normal file
290
n8n-tracker.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
465
tracker-db.json
465
tracker-db.json
@@ -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": []
|
|
||||||
}
|
|
||||||
@@ -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": []
|
|
||||||
}
|
|
||||||
324
tracker.json
324
tracker.json
@@ -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": []
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user