Organizes 11 projects for Cerbo GX/Venus OS into a single repository: - axiom-nmea: Raymarine LightHouse protocol decoder - dbus-generator-ramp: Generator current ramp controller - dbus-lightning: Blitzortung lightning monitor - dbus-meteoblue-forecast: Meteoblue weather forecast - dbus-no-foreign-land: noforeignland.com tracking - dbus-tides: Tide prediction from depth + harmonics - dbus-vrm-history: VRM cloud history proxy - dbus-windy-station: Windy.com weather upload - mfd-custom-app: MFD app deployment package - venus-html5-app: Custom Victron HTML5 app fork - watermaker: Watermaker PLC control UI Adds root README, .gitignore, project template, and per-project .gitignore files. Sensitive config files excluded via .gitignore with .example templates provided. Made-with: Cursor
250 lines
6.6 KiB
Markdown
250 lines
6.6 KiB
Markdown
# NFL (No Foreign Land) API Reference for Location & Tracking
|
||
|
||
This document describes how the No Foreign Land (NFL) API works for submitting boat location and tracking data. Use it to build your own simpler module that does the same thing.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
The NFL API accepts **track data** (sequences of GPS points) via a single HTTP POST endpoint. Each submission includes:
|
||
|
||
1. A **boat API key** (user-specific, from noforeignland.com)
|
||
2. A **timestamp** (milliseconds since epoch)
|
||
3. A **track** (array of `[timestamp_ms, lat, lon]` points)
|
||
|
||
---
|
||
|
||
## API Endpoint
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| **URL** | `https://www.noforeignland.com/home/api/v1/boat/tracking/track` |
|
||
| **Method** | `POST` |
|
||
| **Content-Type** | `application/x-www-form-urlencoded` |
|
||
|
||
---
|
||
|
||
## Authentication
|
||
|
||
### 1. Plugin API Key (hardcoded)
|
||
|
||
The Signal K plugin uses a fixed plugin API key sent in the header:
|
||
|
||
```
|
||
X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97
|
||
```
|
||
|
||
This identifies the plugin/client to the NFL backend. Your own module may need to use the same key or obtain a different one from NFL.
|
||
|
||
### 2. Boat API Key (user-specific)
|
||
|
||
Each boat/user has a **Boat API Key** from noforeignland.com:
|
||
|
||
- **Where to get it:** Account > Settings > Boat tracking > API Key (on the website only, not in the app)
|
||
- **How it’s sent:** As a form field `boatApiKey` in the POST body
|
||
|
||
---
|
||
|
||
## Request Format
|
||
|
||
### POST Body (form-urlencoded)
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `timestamp` | string | Yes | Unix timestamp in **milliseconds** (epoch of the last track point) |
|
||
| `track` | string | Yes | JSON string of track points (see below) |
|
||
| `boatApiKey` | string | Yes | User’s boat API key from noforeignland.com |
|
||
|
||
### Track format
|
||
|
||
`track` is a **JSON string** (so it must be serialized before being sent as form data). Example:
|
||
|
||
```json
|
||
[
|
||
[1234567890000, 52.1234, 4.5678],
|
||
[1234567896000, 52.1240, 4.5680],
|
||
[1234567902000, 52.1245, 4.5685]
|
||
]
|
||
```
|
||
|
||
Each point is a **3-element array**:
|
||
|
||
| Index | Type | Description |
|
||
|-------|------|--------------|
|
||
| 0 | number | Unix timestamp in **milliseconds** |
|
||
| 1 | number | Latitude (WGS84, -90 to 90) |
|
||
| 2 | number | Longitude (WGS84, -180 to 180) |
|
||
|
||
Points should be ordered by time (oldest first).
|
||
|
||
---
|
||
|
||
## Response Format
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "optional message"
|
||
}
|
||
```
|
||
|
||
Or on error:
|
||
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "Error description"
|
||
}
|
||
```
|
||
|
||
### HTTP Status Codes
|
||
|
||
- **200**: Request accepted. Check `status` in the JSON body for success/failure.
|
||
- **4xx**: Client error (e.g. invalid API key, bad request). Do not retry.
|
||
- **5xx**: Server error. Retry with backoff.
|
||
|
||
---
|
||
|
||
## Minimal Example (cURL)
|
||
|
||
```bash
|
||
curl -X POST "https://www.noforeignland.com/home/api/v1/boat/tracking/track" \
|
||
-H "X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "timestamp=1234567902000" \
|
||
-d "track=[[1234567890000,52.1234,4.5678],[1234567896000,52.1240,4.5680],[1234567902000,52.1245,4.5685]]" \
|
||
-d "boatApiKey=YOUR_BOAT_API_KEY"
|
||
```
|
||
|
||
---
|
||
|
||
## Minimal Example (JavaScript/Node.js)
|
||
|
||
```javascript
|
||
const track = [
|
||
[Date.now() - 60000, 52.1234, 4.5678], // 1 min ago
|
||
[Date.now() - 30000, 52.1240, 4.5680], // 30 sec ago
|
||
[Date.now(), 52.1245, 4.5685] // now
|
||
];
|
||
|
||
const lastTimestamp = track[track.length - 1][0];
|
||
|
||
const params = new URLSearchParams();
|
||
params.append('timestamp', String(lastTimestamp));
|
||
params.append('track', JSON.stringify(track));
|
||
params.append('boatApiKey', 'YOUR_BOAT_API_KEY');
|
||
|
||
const response = await fetch('https://www.noforeignland.com/home/api/v1/boat/tracking/track', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-NFL-API-Key': '0ede6cb6-5213-45f5-8ab4-b4836b236f97',
|
||
},
|
||
body: params,
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (response.ok && result.status === 'ok') {
|
||
console.log('Track sent successfully');
|
||
} else {
|
||
console.error('Error:', result.message || response.statusText);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Minimal Example (Python)
|
||
|
||
```python
|
||
import requests
|
||
import json
|
||
import time
|
||
|
||
track = [
|
||
[int(time.time() * 1000) - 60000, 52.1234, 4.5678], # 1 min ago
|
||
[int(time.time() * 1000) - 30000, 52.1240, 4.5680], # 30 sec ago
|
||
[int(time.time() * 1000), 52.1245, 4.5685] # now
|
||
]
|
||
|
||
last_timestamp = track[-1][0]
|
||
|
||
response = requests.post(
|
||
'https://www.noforeignland.com/home/api/v1/boat/tracking/track',
|
||
headers={'X-NFL-API-Key': '0ede6cb6-5213-45f5-8ab4-b4836b236f97'},
|
||
data={
|
||
'timestamp': last_timestamp,
|
||
'track': json.dumps(track),
|
||
'boatApiKey': 'YOUR_BOAT_API_KEY'
|
||
}
|
||
)
|
||
|
||
result = response.json()
|
||
if response.ok and result.get('status') == 'ok':
|
||
print('Track sent successfully')
|
||
else:
|
||
print('Error:', result.get('message', response.text))
|
||
```
|
||
|
||
---
|
||
|
||
## Data Validation (from the Signal K plugin)
|
||
|
||
### Position validation
|
||
|
||
- Latitude: -90 to 90
|
||
- Longitude: -180 to 180
|
||
- Reject non-numeric, NaN, Infinity
|
||
- Reject positions near (0, 0) (likely GPS init values)
|
||
|
||
### Velocity filter (optional)
|
||
|
||
To catch GPS outliers, the plugin rejects points that imply movement faster than a threshold (default 50 m/s ≈ 97 knots):
|
||
|
||
```
|
||
velocity = distance_meters / time_delta_seconds
|
||
```
|
||
|
||
If `velocity > maxVelocity`, the point is dropped.
|
||
|
||
---
|
||
|
||
## 24h Keepalive
|
||
|
||
The plugin does not use a separate keepalive endpoint. Instead:
|
||
|
||
- If a boat has not moved for 24 hours, it still saves a point (using the last known position).
|
||
- That point is included in the next track upload.
|
||
- The effect is that boats stay “active” on NFL even when stationary.
|
||
|
||
---
|
||
|
||
## Retry Logic (from the plugin)
|
||
|
||
- Retry up to 3 times on network errors or 5xx responses.
|
||
- Do not retry on 4xx client errors.
|
||
- Increase timeout per attempt (e.g. 30s, 60s, 90s).
|
||
- Wait 2–4–6 seconds between retries.
|
||
|
||
---
|
||
|
||
## Summary: Minimal Module Checklist
|
||
|
||
| Requirement | Details |
|
||
|-------------|---------|
|
||
| Endpoint | `POST https://www.noforeignland.com/home/api/v1/boat/tracking/track` |
|
||
| Header | `X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97` |
|
||
| Body | `application/x-www-form-urlencoded` with `timestamp`, `track`, `boatApiKey` |
|
||
| Track format | JSON array of `[timestamp_ms, lat, lon]` |
|
||
| Success | `response.status === 200` and `body.status === 'ok'` |
|
||
|
||
---
|
||
|
||
## Source Files Reference
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `src/types/api.ts` | API URL, plugin key, response types |
|
||
| `src/lib/TrackSender.ts` | HTTP request, retry logic, track parsing |
|
||
| `src/lib/TrackLogger.ts` | Position collection, filtering, JSONL storage |
|
||
| `src/types/position.ts` | Internal position format |
|
||
| `src/utils/validation.ts` | Position and velocity checks |
|