Files
venus/dbus-no-foreign-land/NFL_API_REFERENCE.md
dev 9756538f16 Initial commit: Venus OS boat addons monorepo
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
2026-03-16 17:04:16 +00:00

250 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 its 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 | Users 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 246 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 |